MediaWiki:Common.js: Difference between revisions
From Sanatan Hindu Dharma
No edit summary Tag: Reverted |
No edit summary Tag: Manual revert |
||
| Line 459: | Line 459: | ||
if (window.mw && mw.hook) mw.hook('wikipage.content').add(initFullSliders); | if (window.mw && mw.hook) mw.hook('wikipage.content').add(initFullSliders); | ||
})(); | })(); | ||
// --- Disable Tools Option for all users except admin --- | |||
/* ============================================================ | |||
Hide Tools menu for all non-admin users (Chameleon skin) | |||
Show Tools normally for sysop users. | |||
100% reliable version | |||
============================================================ */ | |||
mw.loader.using(['mediawiki.user'], function () { | |||
mw.user.getGroups().then(function (groups) { | |||
var isAdmin = groups.indexOf('sysop') !== -1; | |||
// Add CSS class to <body> for non-admin | |||
if (!isAdmin) { | |||
document.documentElement.classList.add('hide-tools'); | |||
return; | |||
} | |||
// Admin: ensure Tools menu is visible | |||
// Chameleon sometimes loads menu late → use multiple attempts | |||
function showTools() { | |||
// Remove hide class completely | |||
document.documentElement.classList.remove('hide-tools'); | |||
// Explicitly show Tools containers | |||
var dropdown = document.querySelector('.p-tb-dropdown'); | |||
if (dropdown) dropdown.style.display = ''; | |||
var toggleLink = document.querySelector('.p-tb-toggle'); | |||
if (toggleLink) toggleLink.parentElement.style.display = ''; | |||
} | |||
// Try repeatedly to bypass lazy loading | |||
showTools(); | |||
setTimeout(showTools, 200); | |||
setTimeout(showTools, 600); | |||
setTimeout(showTools, 1000); | |||
setTimeout(showTools, 1500); | |||
}); | |||
}); | |||
// == Showing Template as default footer | |||
$(function () { | |||
// Hindi detection | |||
var parts = window.location.pathname.split('/').filter(Boolean); | |||
var isHindi = parts.length > 0 && parts[0].toLowerCase() === 'hi'; | |||
var template = isHindi ? 'Custom-footer-Hindi' : 'Custom-footer'; | |||
// Watcher for footer DOM creation | |||
const observer = new MutationObserver(function () { | |||
// When the footer block is PRESENT and FULLY BUILT | |||
if ($('.footercontainer.container #footer-info').length && | |||
$('.footercontainer.container #footer-places').length) { | |||
// Remove default footer, icons, extra rows | |||
$('.footercontainer.container').remove(); | |||
// Add your custom footer AFTER Chameleon block | |||
$.ajax({ | |||
url: '/index.php?title=Template:' + template + '&action=render', | |||
type: 'GET', | |||
success: function (data) { | |||
// Avoid duplicates | |||
if (!$('#mw-custom-footer').length) { | |||
$('<div id="mw-custom-footer"></div>') | |||
.html(data) | |||
.appendTo('body'); | |||
} | |||
} | |||
}); | |||
// Stop watching | |||
observer.disconnect(); | |||
} | |||
}); | |||
// Now actually watch the body for late additions | |||
observer.observe(document.body, { | |||
childList: true, | |||
subtree: true | |||
}); | |||
}); | |||
// Related_links button mobile | |||
$(document).ready(function () { | |||
var box = $('.related-links-box'); | |||
if (box.length) { | |||
$('body').on('click', '.related-links-box h2', function () { | |||
box.toggleClass('open'); | |||
}); | |||
} | |||
}); | |||
//Category list show more button | |||
$(document).ready(function () { | |||
$('.show-all-categories').on('click', function () { | |||
$('.category-full-list').slideDown(200); | |||
$(this).hide(); | |||
}); | |||
}); | |||
// Latest article Creation UI | |||
$(document).ready(function () { | |||
// Only run on this page | |||
if (mw.config.get("wgPageName") !== "LatestArticlesUpdate") return; | |||
console.log("Latest Articles Editor Loaded (ES5)"); | |||
// Load JSON from page | |||
new mw.Api().get({ | |||
action: "query", | |||
titles: "LatestArticlesJSON", | |||
prop: "revisions", | |||
rvprop: "content", | |||
formatversion: "2" | |||
}).done(function (res) { | |||
var content = res.query.pages[0].revisions[0].content || "[]"; | |||
var data = []; | |||
try { | |||
data = JSON.parse(content); | |||
} catch (e) {} | |||
// Prefill boxes | |||
for (var i = 0; i < data.length; i++) { | |||
$("#title" + (i + 1)).val(data[i].title); | |||
$("#link" + (i + 1)).val(data[i].link); | |||
} | |||
}); | |||
// SAVE BUTTON CLICK | |||
$("#saveLatestArticlesBtn").click(function () { | |||
var items = []; | |||
var i, t, l; | |||
for (i = 1; i <= 10; i++) { | |||
t = $("#title" + i).val().trim(); | |||
l = $("#link" + i).val().trim(); | |||
if (t !== "" && l !== "") { | |||
items.push({ title: t, link: l }); | |||
} | |||
} | |||
// Save JSON back to storage page | |||
new mw.Api().postWithEditToken({ | |||
action: "edit", | |||
title: "LatestArticlesJSON", | |||
text: JSON.stringify(items, null, 2), | |||
summary: "Updated Latest Articles" | |||
}).done(function () { | |||
$("#saveStatus").html("<b style='color:green;'>✔ Saved successfully!</b>"); | |||
}); | |||
}); | |||
}); | |||
// To show updated articles in Frontpage | |||
mw.hook("wikipage.content").add(function () { | |||
// Run only if div exists | |||
if (!document.getElementById("LatestArticlesOutput")) return; | |||
console.log("Homepage Latest Articles Loader Running"); | |||
new mw.Api().get({ | |||
action: "query", | |||
titles: "LatestArticlesJSON", | |||
prop: "revisions", | |||
rvprop: "content", | |||
formatversion: "2" | |||
}).done(function (res) { | |||
var content = res.query.pages[0].revisions[0].content || "[]"; | |||
var data = []; | |||
try { data = JSON.parse(content); } catch(e){} | |||
var out = $("#LatestArticlesOutput"); | |||
out.html(""); | |||
for (var i = 0; i < data.length; i++) { | |||
var row = data[i]; | |||
out.append( | |||
'<a class="latestArticleBtn" href="' + row.link + '">' + | |||
row.title + | |||
'</a>' | |||
); | |||
} | |||
}); | |||
}); | |||
// Paste this into MediaWiki:Common.js | |||
mw.loader.using('ext.visualEditor.core').then(function () { | |||
// Small helper: detect domain-like text (gives true for "example.com", "sub.example.co", "example.com/path") | |||
function looksLikeDomain(text) { | |||
if (!text) return false; | |||
// allow www., allow path after TLD, basic heuristic | |||
return /\b([a-z0-9-]+\.)+[a-z]{2,}(\/\S*)?/i.test(text.trim()); | |||
} | |||
// Normalize a domain-like text into an https:// URL | |||
function buildExternalFromText(text) { | |||
text = (text || '').trim(); | |||
if (!text) return null; | |||
// If already has protocol, return as-is | |||
if (/^\w+:\/\//.test(text)) return text; | |||
// Remove leading slashes if VE added them (e.g. "/Writerpage1/gulfplacement.com") | |||
text = text.replace(/^\/+/, ''); | |||
// If text contains ' ' (label + url), try to extract domain-like chunk | |||
var match = text.match(/([a-z0-9-]+\.)+[a-z]{2,}(\/\S*)?/i); | |||
if (match) { | |||
return 'https://' + match[0]; | |||
} | |||
return 'https://' + text; | |||
} | |||
function ensureExternalAnchorsInSurface(surface) { | |||
if (!surface || !surface.$element) return function () {}; | |||
var $el = surface.$element; | |||
var observer = null; | |||
var repeatTimer = null; | |||
var repeatCount = 0; | |||
var MAX_REPEAT = 30; // ~30 * 200ms = 6s of retries | |||
// fix anchors that VE rewrote into internal | |||
function fixAnchorsOnce() { | |||
try { | |||
$el.find('a').each(function () { | |||
var a = this; | |||
var href = a.getAttribute('href') || ''; | |||
var text = a.textContent || a.innerText || ''; | |||
// If href is relative or missing protocol AND the visible link text looks like a domain | |||
if (href && !/^\w+:\/\//.test(href) && looksLikeDomain(text)) { | |||
var newHref = buildExternalFromText(text); | |||
if (newHref) { | |||
a.setAttribute('href', newHref); | |||
// mark so we don't process again | |||
a.setAttribute('data-external-forced', '1'); | |||
// show as external in VE dialog as well by adding rel | |||
a.setAttribute('rel', 'noopener noreferrer'); | |||
} | |||
} | |||
// Some VE-created anchors may have href like "/Writerpage1/gulfplacement.com" or "Writerpage1/gulfplacement.com" | |||
// The above logic catches those since href lacks protocol and text contains domain-like string. | |||
}); | |||
} catch (e) { | |||
// ignore | |||
console.log('fixAnchorsOnce error', e); | |||
} | |||
} | |||
// Start a short-lived repetition (overrides background rewrite that runs after a delay) | |||
function startRepeater() { | |||
repeatCount = 0; | |||
if (repeatTimer) { | |||
clearInterval(repeatTimer); | |||
} | |||
repeatTimer = setInterval(function () { | |||
fixAnchorsOnce(); | |||
repeatCount++; | |||
if (repeatCount >= MAX_REPEAT) { | |||
clearInterval(repeatTimer); | |||
repeatTimer = null; | |||
} | |||
}, 200); | |||
} | |||
// MutationObserver to catch transform events quickly | |||
try { | |||
observer = new MutationObserver(function () { | |||
fixAnchorsOnce(); | |||
}); | |||
observer.observe($el[0], { childList: true, subtree: true, attributes: true }); | |||
} catch (e) { | |||
// MutationObserver not available? fallback to simple interval already used above | |||
console.log('MutationObserver not available', e); | |||
} | |||
// return cleanup function (if needed) | |||
return function cleanup() { | |||
if (observer) { | |||
try { observer.disconnect(); } catch (e) {} | |||
observer = null; | |||
} | |||
if (repeatTimer) { | |||
clearInterval(repeatTimer); | |||
repeatTimer = null; | |||
} | |||
}; | |||
} | |||
// Wait for VE to initialize and then attach paste handler and anchor-fixer | |||
ve.init.platform.addReadyCallback(function () { | |||
try { | |||
var surface = ve.init.target.getSurface(); | |||
if (!surface || !surface.$element) { | |||
return; | |||
} | |||
// Disable as many auto-internal-link hooks as reasonable client-side (best-effort) | |||
try { | |||
if (ve.ui && ve.ui.MWInternalLinkAnnotation) { | |||
if (ve.ui.MWInternalLinkAnnotation.static) { | |||
ve.ui.MWInternalLinkAnnotation.static.matchFunction = function () { return false; }; | |||
} | |||
} | |||
if (ve.dm && ve.dm.MWInternalLinkAnnotation && ve.dm.MWInternalLinkAnnotation.static) { | |||
ve.dm.MWInternalLinkAnnotation.static.matchRdfaTypes = []; | |||
ve.dm.MWInternalLinkAnnotation.static.matchFunction = function () { return false; }; | |||
} | |||
if (ve.ce && ve.ce.MWInternalLinkAnnotation && ve.ce.MWInternalLinkAnnotation.static) { | |||
ve.ce.MWInternalLinkAnnotation.static.matchRdfaTypes = []; | |||
} | |||
} catch (err) { | |||
// ignore if internals differ | |||
console.log('VE annotator override error', err); | |||
} | |||
// Attach paste handler | |||
surface.$element.on('paste', function (e) { | |||
try { | |||
var data = (e.originalEvent || e).clipboardData; | |||
if (!data) return; | |||
var text = data.getData('text/plain'); | |||
if (!text) return; | |||
// Prevent default paste (we will insert cleaned text) | |||
e.preventDefault(); | |||
// Convert any URLs to explicit external link syntax in the pasted text | |||
// This helps VE treat them as external when inserted | |||
text = text.replace(/https?:\/\/[^\s]+/g, function (url) { | |||
return '[' + url + ']'; | |||
}); | |||
// Insert cleaned text into VE surface model (best-effort simple insertion) | |||
try { | |||
// Use VE model insertion if available | |||
if (surface.model && surface.model.getFragment) { | |||
surface.model.getFragment().insertContent(text); | |||
} else { | |||
// fallback to inserting into contenteditable DOM | |||
document.execCommand('insertText', false, text); | |||
} | |||
} catch (insertErr) { | |||
// fallback DOM insert | |||
document.execCommand('insertText', false, text); | |||
} | |||
// Immediately run anchor fixer and start repeating to defeat delayed rewriter | |||
var cleanup = ensureExternalAnchorsInSurface(surface); | |||
// ensure cleanup after a while (we already limit repeats inside ensureExternalAnchorsInSurface) | |||
setTimeout(function () { | |||
if (typeof cleanup === 'function') { | |||
try { cleanup(); } catch (e) {} | |||
} | |||
}, 8000); // give it up to 8s (safe upper bound) then cleanup | |||
// also start immediate fight-back | |||
// (the internal ensureExternalAnchorsInSurface already schedules repeats) | |||
} catch (outerE) { | |||
console.log('paste handler error', outerE); | |||
} | |||
}); | |||
// Extra safety: run an initial pass on load to correct any existing bad anchors | |||
try { | |||
setTimeout(function () { | |||
ensureExternalAnchorsInSurface(surface); | |||
}, 500); | |||
} catch (e) { | |||
// ignore | |||
} | |||
} catch (e) { | |||
console.log('VE addReadyCallback error', e); | |||
} | |||
}); | |||
}); | |||
//Showing SEO button in Visual Editor | |||
(function () { | |||
mw.loader.using([ | |||
'ext.visualEditor.desktopArticleTarget.init', | |||
'oojs-ui', | |||
'jquery', | |||
'mediawiki.api' | |||
]).done(function () { | |||
/* -------------------------------------------------- | |||
Add "SEO" button to VisualEditor toolbar | |||
-------------------------------------------------- */ | |||
if (mw.config.get('wgIsArticle')) { | |||
mw.hook('ve.activationComplete').add(function () { | |||
try { | |||
var veTarget = ve.init && ve.init.target; | |||
var toolbar = veTarget && veTarget.toolbar; | |||
if (!toolbar) return; | |||
if (toolbar.$element.find('.ve-ui-toolbar-seoButton').length) return; | |||
var seoButton = new OO.ui.ButtonWidget({ | |||
label: 'SEO', | |||
icon: 'settings', | |||
classes: ['ve-ui-toolbar-seoButton'] | |||
}); | |||
seoButton.on('click', function () { | |||
openSEODialog(veTarget); | |||
}); | |||
toolbar.$element.find('.oo-ui-toolbar-actions').append(seoButton.$element); | |||
} catch (e) { | |||
console.error('[SEO] Toolbar init error:', e); | |||
} | |||
}); | |||
} | |||
/* -------------------------------------------------- | |||
SEO Dialog | |||
-------------------------------------------------- */ | |||
function openSEODialog(veTarget) { | |||
var surface = veTarget.surface; | |||
var model = surface.getModel(); | |||
var docText = ''; | |||
try { | |||
docText = model.getDocument().data.getText(true); | |||
} catch (e) { | |||
var txt = document.getElementById('wpTextbox1'); | |||
if (txt) docText = txt.value; | |||
} | |||
// Extract existing SEO comment | |||
var oldTitle = '', oldDesc = '', oldKeys = ''; | |||
var match = /<!--\s*SEO\s+title=(.*?)\s+description=([\s\S]*?)\s+keywords=([\s\S]*?)\s*-->/i.exec(docText); | |||
if (match) { | |||
oldTitle = match[1].trim(); | |||
oldDesc = match[2].trim(); | |||
oldKeys = match[3].trim(); | |||
} | |||
// Input fields | |||
var titleInput = new OO.ui.TextInputWidget({ placeholder: 'Meta Title', value: oldTitle }); | |||
var descInput = new OO.ui.MultilineTextInputWidget({ placeholder: 'Meta Description', rows: 3, value: oldDesc }); | |||
var keysInput = new OO.ui.TextInputWidget({ placeholder: 'Meta Keywords', value: oldKeys }); | |||
// Create Dialog | |||
var SEODialog = function (cfg) { SEODialog.super.call(this, cfg); }; | |||
OO.inheritClass(SEODialog, OO.ui.ProcessDialog); | |||
SEODialog.static.name = 'seoDialog'; | |||
SEODialog.static.title = 'SEO Settings'; | |||
SEODialog.static.size = 'larger'; | |||
SEODialog.static.actions = [ | |||
{ action: 'save', label: 'Save', flags: ['primary', 'progressive'] }, | |||
{ action: 'cancel', label: 'Cancel', flags: ['safe'] } | |||
]; | |||
SEODialog.prototype.initialize = function () { | |||
SEODialog.super.prototype.initialize.apply(this, arguments); | |||
var panel = new OO.ui.PanelLayout({ padded: true, expanded: true }); | |||
panel.$element.append( | |||
$('<label>').text('Meta Title'), | |||
titleInput.$element, $('<br><br>'), | |||
$('<label>').text('Meta Description'), | |||
descInput.$element, $('<br><br>'), | |||
$('<label>').text('Meta Keywords'), | |||
keysInput.$element | |||
); | |||
this.$body.append(panel.$element); | |||
}; | |||
SEODialog.prototype.getBodyHeight = function () { | |||
return 420; | |||
}; | |||
/* -------------------------------------------------- | |||
FINAL FIX: SAFE SAVE USING API | |||
(DO NOT CHANGE VE CONTENT) | |||
-------------------------------------------------- */ | |||
SEODialog.prototype.getActionProcess = function (action) { | |||
var dialog = this; | |||
if (action === 'save') { | |||
return new OO.ui.Process(function () { | |||
var t = titleInput.getValue().trim(); | |||
var d = descInput.getValue().trim(); | |||
var k = keysInput.getValue().trim(); | |||
// New SEO block | |||
var newSEO = | |||
"<!--SEO title=\"" + mw.html.escape(t) + | |||
"\" description=\"" + mw.html.escape(d) + | |||
"\" keywords=\"" + mw.html.escape(k) + | |||
"\" -->"; | |||
var api = new mw.Api(); | |||
// STEP 1: Load TRUE source wikitext (not VE text) | |||
api.get({ | |||
action: "query", | |||
prop: "revisions", | |||
rvprop: "content", | |||
formatversion: "2", | |||
titles: mw.config.get("wgPageName") | |||
}).done(function (data) { | |||
var page = data.query.pages[0]; | |||
var source = page.revisions[0].content; | |||
// STEP 2: Remove old SEO block | |||
source = source.replace(/<!--SEO[\s\S]*?-->/i, ""); | |||
// STEP 3: Add new SEO at very top | |||
source = newSEO + "\n" + source; | |||
// STEP 4: Save back safely | |||
api.postWithToken("csrf", { | |||
action: "edit", | |||
title: mw.config.get("wgPageName"), | |||
text: source, | |||
summary: "Updated SEO metadata" | |||
}).done(function () { | |||
mw.notify("✔ SEO updated successfully"); | |||
location.reload(); | |||
}); | |||
}); | |||
dialog.close(); | |||
}); | |||
} | |||
if (action === 'cancel') { | |||
return new OO.ui.Process(function () { | |||
dialog.close(); | |||
}); | |||
} | |||
}; | |||
// -------------------------------------------------- | |||
// Open dialog | |||
// -------------------------------------------------- | |||
var wm = window._seoWM || new OO.ui.WindowManager(); | |||
if (!window._seoWM) { | |||
window._seoWM = wm; | |||
$(document.body).append(wm.$element); | |||
} | |||
var dlg = new SEODialog(); | |||
wm.addWindows([dlg]); | |||
wm.openWindow(dlg); | |||
} | |||
}); | |||
})(); | |||
/* ==================================================================== | |||
VISUALEDITOR – Writer Tools (Common.js Edition) | |||
Word Count, Character Count, Reading Time | |||
With Close/Open Button | |||
ES5 Compatible | MediaWiki 1.39.x | |||
==================================================================== */ | |||
(function () { | |||
if (window.WT_Loaded) return; | |||
window.WT_Loaded = true; | |||
var PANEL_ID = 'wt-ve-panel'; | |||
var WPM = 200; // words per minute reading speed | |||
var updateTimeout = null; | |||
var surfaceRef = null; | |||
/* -------------------------------------------------------------- | |||
Create Writer Tools Panel + Open Button | |||
-------------------------------------------------------------- */ | |||
function createPanel() { | |||
if (document.getElementById(PANEL_ID)) return; | |||
/* Main Panel */ | |||
var panel = document.createElement('div'); | |||
panel.id = PANEL_ID; | |||
panel.style.position = 'fixed'; | |||
panel.style.left = '20px'; | |||
panel.style.bottom = '20px'; | |||
panel.style.padding = '12px 16px'; | |||
panel.style.fontSize = '13px'; | |||
panel.style.background = '#fff'; | |||
panel.style.border = '1px solid #ccc'; | |||
panel.style.borderRadius = '8px'; | |||
panel.style.boxShadow = '0 2px 10px rgba(0,0,0,0.1)'; | |||
panel.style.zIndex = '2000'; | |||
panel.style.minWidth = '220px'; | |||
/* Close Button (X) */ | |||
var closeBtn = document.createElement('div'); | |||
closeBtn.textContent = '✕'; | |||
closeBtn.style.position = 'absolute'; | |||
closeBtn.style.top = '4px'; | |||
closeBtn.style.right = '8px'; | |||
closeBtn.style.cursor = 'pointer'; | |||
closeBtn.style.fontSize = '14px'; | |||
closeBtn.style.color = '#666'; | |||
closeBtn.onclick = function () { | |||
panel.style.display = 'none'; | |||
document.getElementById('wt-open-btn').style.display = 'block'; | |||
}; | |||
panel.appendChild(closeBtn); | |||
/* Title */ | |||
var title = document.createElement('div'); | |||
title.textContent = 'Writer Tools'; | |||
title.style.fontWeight = 'bold'; | |||
title.style.marginBottom = '6px'; | |||
panel.appendChild(title); | |||
/* Stats */ | |||
var wordsDiv = document.createElement('div'); | |||
wordsDiv.id = 'wt-words'; | |||
wordsDiv.textContent = 'Words: 0'; | |||
panel.appendChild(wordsDiv); | |||
var charsDiv = document.createElement('div'); | |||
charsDiv.id = 'wt-chars'; | |||
charsDiv.textContent = 'Characters: 0'; | |||
panel.appendChild(charsDiv); | |||
var rtDiv = document.createElement('div'); | |||
rtDiv.id = 'wt-rt'; | |||
rtDiv.textContent = 'Reading time: 0 min'; | |||
panel.appendChild(rtDiv); | |||
document.body.appendChild(panel); | |||
/* OPEN Button (Floating) */ | |||
var openBtn = document.createElement('div'); | |||
openBtn.id = 'wt-open-btn'; | |||
openBtn.textContent = 'Writer Tools'; | |||
openBtn.style.position = 'fixed'; | |||
openBtn.style.left = '20px'; | |||
openBtn.style.bottom = '20px'; | |||
openBtn.style.padding = '6px 10px'; | |||
openBtn.style.background = '#0366d6'; | |||
openBtn.style.color = '#fff'; | |||
openBtn.style.borderRadius = '6px'; | |||
openBtn.style.cursor = 'pointer'; | |||
openBtn.style.fontSize = '12px'; | |||
openBtn.style.zIndex = '2001'; | |||
openBtn.style.display = 'none'; | |||
openBtn.onclick = function () { | |||
panel.style.display = 'block'; | |||
openBtn.style.display = 'none'; | |||
}; | |||
document.body.appendChild(openBtn); | |||
} | |||
/* -------------------------------------------------------------- | |||
Text Utilities | |||
-------------------------------------------------------------- */ | |||
function cleanText(str) { | |||
if (!str) return ''; | |||
str = str.replace(/\u00A0/g, ' '); // non-breaking spaces | |||
return str.replace(/\s+/g, ' ').trim(); | |||
} | |||
function countWords(str) { | |||
if (!str) return 0; | |||
var arr = str.split(/\s+/); | |||
var c = 0; | |||
for (var i = 0; i < arr.length; i++) { | |||
if (/[A-Za-z0-9]/.test(arr[i])) c++; | |||
} | |||
return c; | |||
} | |||
function countChars(str) { | |||
return str ? str.length : 0; | |||
} | |||
/* -------------------------------------------------------------- | |||
Update Panel Stats | |||
-------------------------------------------------------------- */ | |||
function updateStats() { | |||
if (!surfaceRef) return; | |||
try { | |||
var view = surfaceRef.getView(); | |||
if (!view) return; | |||
var text = view.$element.text() || ''; | |||
text = cleanText(text); | |||
var words = countWords(text); | |||
var chars = countChars(text); | |||
var minutes = Math.ceil(words / WPM); | |||
if (minutes < 1) minutes = 1; | |||
document.getElementById('wt-words').textContent = 'Words: ' + words; | |||
document.getElementById('wt-chars').textContent = 'Characters: ' + chars; | |||
document.getElementById('wt-rt').textContent = 'Reading time: ' + minutes + ' min'; | |||
} catch (err) { | |||
console.error('WriterTools update error:', err); | |||
} | |||
} | |||
/* -------------------------------------------------------------- | |||
Attach to VisualEditor surface | |||
-------------------------------------------------------------- */ | |||
function attachToVE() { | |||
try { | |||
surfaceRef = null; | |||
if ( | |||
ve && | |||
ve.init && | |||
ve.init.target && | |||
ve.init.target.getSurface | |||
) { | |||
surfaceRef = ve.init.target.getSurface(); | |||
} | |||
if (!surfaceRef) return; | |||
createPanel(); | |||
updateStats(); | |||
/* Update while typing (throttle 200ms) */ | |||
surfaceRef.getModel().on('transact', function () { | |||
if (updateTimeout) clearTimeout(updateTimeout); | |||
updateTimeout = setTimeout(updateStats, 200); | |||
}); | |||
} catch (e) { | |||
console.error('WriterTools attach error:', e); | |||
} | |||
} | |||
/* -------------------------------------------------------------- | |||
Trigger when VE activates | |||
-------------------------------------------------------------- */ | |||
mw.hook('ve.activationComplete').add(function () { | |||
setTimeout(attachToVE, 400); | |||
}); | |||
})(); | |||
// Team Dashboard Settings | |||
mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(function () { | |||
(function ($) { | |||
/** ---------- Utility: escape HTML ---------- **/ | |||
function escapeHtml(str) { | |||
return String(str) | |||
.replace(/&/g, '&') | |||
.replace(/</g, '<') | |||
.replace(/>/g, '>') | |||
.replace(/"/g, '"') | |||
.replace(/'/g, '''); | |||
} | |||
/** ---------- Detect Allowed Special Pages ---------- **/ | |||
var pageName = (mw.config.get('wgCanonicalSpecialPageName') || '').toLowerCase(); | |||
var allowedPages = ['teamdashboard', 'approvedpages']; | |||
if (!allowedPages.includes(pageName)) return; | |||
/** ---------- Accordion UI (with LocalStorage) ---------- **/ | |||
function initAccordionOnce() { | |||
$('.accordion-toggle').each(function () { | |||
var $el = $(this); | |||
var targetSel = $el.attr('data-target'); | |||
var $target = $(targetSel); | |||
if (!$target.length) return; | |||
if ($el.find('.pa-arrow').length === 0) { | |||
$el.prepend('<span class="pa-arrow">▶</span> '); | |||
} | |||
var saved = localStorage.getItem('pa:' + targetSel); | |||
if (saved === 'open') { | |||
$target.show(); | |||
$el.addClass('open'); | |||
$el.find('.pa-arrow').text('▼ '); | |||
} else { | |||
$target.hide(); | |||
$el.removeClass('open'); | |||
$el.find('.pa-arrow').text('▶ '); | |||
} | |||
if (!$el.data('pa-bound')) { | |||
$el.on('click', function () { | |||
var isOpen = $el.hasClass('open'); | |||
if (isOpen) { | |||
$el.removeClass('open'); | |||
$target.slideUp(150); | |||
$el.find('.pa-arrow').text('▶ '); | |||
localStorage.setItem('pa:' + targetSel, 'closed'); | |||
} else { | |||
$el.addClass('open'); | |||
$target.slideDown(150); | |||
$el.find('.pa-arrow').text('▼ '); | |||
localStorage.setItem('pa:' + targetSel, 'open'); | |||
} | |||
}); | |||
$el.data('pa-bound', true); | |||
} | |||
}); | |||
} | |||
/** ---------- MediaWiki API ---------- **/ | |||
var api = new mw.Api(); | |||
function getFreshToken() { | |||
return api | |||
.get({ | |||
action: 'query', | |||
meta: 'tokens', | |||
type: 'csrf', | |||
format: 'json' | |||
}) | |||
.then(function (d) { | |||
return d.query.tokens.csrftoken || ''; | |||
}) | |||
.catch(function () { | |||
return ''; | |||
}); | |||
} | |||
/** ---------- POST helper ---------- **/ | |||
function postAction(fd) { | |||
return $.ajax({ | |||
url: mw.util.getUrl('Special:TeamDashboard'), | |||
type: 'POST', | |||
data: fd, | |||
processData: false, | |||
contentType: false, | |||
headers: { 'X-Requested-With': 'XMLHttpRequest' } | |||
}); | |||
} | |||
/** ---------- Approve / Reject ---------- **/ | |||
$(document).on('click', 'button[data-action]', function (e) { | |||
e.preventDefault(); | |||
var $btn = $(this); | |||
if ($btn.prop('disabled')) return; | |||
var action = $btn.data('action'), | |||
pageId = $btn.data('page-id'); | |||
$btn.prop('disabled', true); | |||
getFreshToken().then(function (token) { | |||
if (!token) { | |||
alert('Session expired, reload.'); | |||
$btn.prop('disabled', false); | |||
return; | |||
} | |||
var fd = new FormData(); | |||
fd.append('pa_action', action); | |||
fd.append('page_id', pageId); | |||
fd.append('token', token); | |||
postAction(fd) | |||
.done(function () { | |||
if (action === 'approve') { | |||
$btn.text('Approved ✓').prop('disabled', true); | |||
$btn.closest('td').prev().prev().text('approved'); | |||
} else if (action === 'reject') { | |||
$btn.text('Rejected ✗').prop('disabled', true); | |||
$btn.closest('td').prev().prev().text('rejected'); | |||
} | |||
}) | |||
.fail(function () { | |||
alert('Action failed. Please try again.'); | |||
$btn.prop('disabled', false); | |||
}); | |||
}); | |||
}); | |||
/** ---------- Comment submit ---------- **/ | |||
$(document).on('click', '.comment-submit', function (e) { | |||
e.preventDefault(); | |||
var $btn = $(this); | |||
if ($btn.prop('disabled')) return; | |||
var $wrap = $btn.closest('.ajax-comment-wrap'), | |||
txt = $.trim($wrap.find('input.comment-input').val() || ''), | |||
pageId = $btn.data('page-id'); | |||
if (txt === '') return; | |||
$btn.prop('disabled', true); | |||
getFreshToken().then(function (token) { | |||
if (!token) { | |||
alert('Session expired, reload.'); | |||
$btn.prop('disabled', false); | |||
return; | |||
} | |||
var fd = new FormData(); | |||
fd.append('pa_action', 'comment'); | |||
fd.append('page_id', pageId); | |||
fd.append('token', token); | |||
fd.append('comment', txt); | |||
postAction(fd) | |||
.done(function () { | |||
var $td = $btn.closest('td'); | |||
var $list = $td.find('.comment-list'); | |||
if (!$list.length) { | |||
var box = $('<div class="comment-box"><b>Comments</b><ul class="comment-list"></ul></div>'); | |||
$td.append(box); | |||
$list = box.find('.comment-list'); | |||
} | |||
var username = mw.config.get('wgUserName') || 'You'; | |||
var now = new Date(); | |||
var istOffset = 5.5 * 60 * 60 * 1000; | |||
var ist = new Date(now.getTime() + istOffset); | |||
var stamp = | |||
ist.getFullYear() + | |||
'-' + | |||
String(ist.getMonth() + 1).padStart(2, '0') + | |||
'-' + | |||
String(ist.getDate()).padStart(2, '0') + | |||
' ' + | |||
String(ist.getHours()).padStart(2, '0') + | |||
':' + | |||
String(ist.getMinutes()).padStart(2, '0') + | |||
':' + | |||
String(ist.getSeconds()).padStart(2, '0') + | |||
' IST'; | |||
var li = $('<li/>').html( | |||
'<b>' + | |||
escapeHtml(username) + | |||
'</b> <span class="date">(' + | |||
stamp + | |||
')</span><br>' + | |||
escapeHtml(txt) + | |||
' <button class="edit-comment-btn">✏️</button>' | |||
); | |||
$list.prepend(li); | |||
$wrap.find('input.comment-input').val(''); | |||
}) | |||
.always(function () { | |||
$btn.prop('disabled', false); | |||
}); | |||
}); | |||
}); | |||
/** ---------- Delete comment ---------- **/ | |||
$(document).on('click', '.delete-comment-btn', function (e) { | |||
e.preventDefault(); | |||
var $btn = $(this); | |||
if ($btn.prop('disabled')) return; | |||
if (!confirm('Delete this comment?')) return; | |||
var commentId = $btn.data('comment-id'), | |||
pageId = $btn.data('page-id'); | |||
$btn.prop('disabled', true); | |||
getFreshToken().then(function (token) { | |||
if (!token) { | |||
alert('Session expired.'); | |||
$btn.prop('disabled', false); | |||
return; | |||
} | |||
var fd = new FormData(); | |||
fd.append('pa_action', 'delete_comment'); | |||
fd.append('comment_id', commentId); | |||
fd.append('page_id', pageId); | |||
fd.append('token', token); | |||
postAction(fd) | |||
.done(function () { | |||
$btn.closest('li').fadeOut(200, function () { | |||
$(this).remove(); | |||
}); | |||
}) | |||
.always(function () { | |||
$btn.prop('disabled', false); | |||
}); | |||
}); | |||
}); | |||
/** ---------- FIXED: Edit comment handler ---------- **/ | |||
$(document).on('click', '.edit-comment-btn', function () { | |||
var $btn = $(this); | |||
var $li = $btn.closest('li'); | |||
var oldText = $li.clone().children().remove().end().text().trim(); | |||
var pageId = $li.closest('td').find('.comment-submit').data('page-id'); | |||
var commentId = $btn.data('comment-id'); | |||
if ($li.find('.edit-comment-area').length) return; | |||
var $area = $('<textarea class="edit-comment-area"></textarea>').val(oldText); | |||
var $save = $('<button class="pa-btn save-edit">Save</button>'); | |||
var $cancel = $('<button class="pa-btn danger cancel-edit">Cancel</button>'); | |||
$li.append($area, $save, $cancel); | |||
$save.on('click', function () { | |||
var newText = $.trim($area.val()); | |||
if (!newText) return alert('Comment cannot be empty.'); | |||
getFreshToken().then(function (token) { | |||
var fd = new FormData(); | |||
fd.append('pa_action', 'edit_comment'); | |||
fd.append('comment_id', commentId); | |||
fd.append('page_id', pageId); | |||
fd.append('comment', newText); | |||
fd.append('token', token); | |||
postAction(fd) | |||
.done(function () { | |||
alert('Comment updated successfully.'); | |||
$area.remove(); | |||
$save.remove(); | |||
$cancel.remove(); | |||
$btn.replaceWith( | |||
'<span>' + | |||
escapeHtml(newText) + | |||
' <em>(edited)</em></span>' | |||
); | |||
}) | |||
.fail(function () { | |||
alert('Comment updated successfully.'); | |||
}); | |||
}); | |||
}); | |||
$cancel.on('click', function () { | |||
$area.remove(); | |||
$save.remove(); | |||
$cancel.remove(); | |||
}); | |||
}); | |||
/** ---------- Search Filter (Dashboard + ApprovedPages) ---------- **/ | |||
$(document).on('input', '.pa-search', function () { | |||
var q = $(this).val().toLowerCase(); | |||
$('.approved-table tbody tr, .dashboard-table tbody tr').each(function () { | |||
$(this).toggle($(this).text().toLowerCase().indexOf(q) !== -1); | |||
}); | |||
}); | |||
/** ---------- EXTRA SEARCH FOR ACCORDION (Leads + Writers) ---------- **/ | |||
$(document).on('input', '.pa-search', function () { | |||
var q = $(this).val().toLowerCase(); | |||
// 1. Filter approved + pending tables (existing behavior) | |||
$('.approved-table tbody tr, .dashboard-table tbody tr').each(function () { | |||
$(this).toggle($(this).text().toLowerCase().indexOf(q) !== -1); | |||
}); | |||
// 2. Filter accordion lead items | |||
$('.accordion-item').each(function () { | |||
var txt = $(this).text().toLowerCase(); | |||
if (txt.indexOf(q) !== -1) { | |||
$(this).show(); | |||
} else { | |||
$(this).hide(); | |||
} | |||
}); | |||
}); | |||
/** ---------- Sort Approved Pages ---------- **/ | |||
$(document).on('change', '.pa-sort', function () { | |||
var mode = $(this).val(); | |||
var rows = $('.approved-table tbody tr').get(); | |||
rows.sort(function (a, b) { | |||
if (mode === 'az') { | |||
var ta = $(a).find('.title').text().toLowerCase(); | |||
var tb = $(b).find('.title').text().toLowerCase(); | |||
return ta.localeCompare(tb); | |||
} | |||
if (mode === 'updated') { | |||
var da = new Date($(a).data('updated')); | |||
var db = new Date($(b).data('updated')); | |||
return db - da; // newest first | |||
} | |||
return 0; | |||
}); | |||
$.each(rows, function (_, row) { | |||
$('.approved-table tbody').append(row); | |||
}); | |||
}); | |||
/** ---------- Init ---------- **/ | |||
initAccordionOnce(); | |||
setTimeout(initAccordionOnce, 700); | |||
})(jQuery); | |||
}); | |||
/** ---------- Article Header Sort (Pending Pages) ---------- **/ | |||
$(document).on('change', '.pa-article-sort', function () { | |||
var mode = $(this).val(); | |||
var $table = $(this).closest('table'); | |||
var $tbody = $table.find('tbody'); | |||
if (!$tbody.length) return; | |||
var rows = $tbody.find('tr').get(); | |||
rows.sort(function (a, b) { | |||
var ta = $(a).data('title'); | |||
var tb = $(b).data('title'); | |||
var da = new Date($(a).data('time')); | |||
var db = new Date($(b).data('time')); | |||
if (mode === 'az') return ta.localeCompare(tb); | |||
if (mode === 'za') return tb.localeCompare(ta); | |||
if (mode === 'oldest') return da - db; | |||
if (mode === 'newest') return db - da; | |||
return 0; | |||
}); | |||
$.each(rows, function (_, row) { | |||
$tbody.append(row); | |||
}); | |||
}); | |||
(function () { | |||
// =================================================================== | |||
// A) FLOATING CREATE BUTTON UI (unchanged, ES5) | |||
// =================================================================== | |||
function initCreateButton() { | |||
if (typeof mw === 'undefined') return; | |||
if (!mw.config.get('wgUserName')) return; | |||
var groups = mw.config.get('wgUserGroups') || []; | |||
var isEditor = groups.indexOf('editor') !== -1; | |||
var isAdmin = groups.indexOf('sysop') !== -1; | |||
var isWriter = groups.indexOf('writer') !== -1; | |||
var isLead = groups.indexOf('lead') !== -1; | |||
// Only Writers, Leads, Editors, Admins can use UI | |||
if (!(isWriter || isLead || isEditor || isAdmin)) return; | |||
// Avoid duplicate button | |||
if (document.getElementById('floatingCreateBtn')) return; | |||
// Floating button | |||
var btn = document.createElement('button'); | |||
btn.id = 'floatingCreateBtn'; | |||
btn.appendChild(document.createTextNode('+')); | |||
btn.style.cssText = | |||
'position:fixed;bottom:30px;right:30px;width:60px;height:60px;' + | |||
'border-radius:50%;background:#0078D7;color:#fff;font-size:32px;' + | |||
'border:none;cursor:pointer;z-index:9999;box-shadow:0 4px 10px rgba(0,0,0,0.3);'; | |||
btn.onmouseover = function () { btn.style.background = '#005fa3'; }; | |||
btn.onmouseout = function () { btn.style.background = '#0078D7'; }; | |||
document.body.appendChild(btn); | |||
// Popup box | |||
var popup = document.createElement('div'); | |||
popup.id = 'createPopupBox'; | |||
popup.style.cssText = | |||
'position:fixed;bottom:100px;right:30px;background:#fff;padding:15px;' + | |||
'border:2px solid #ccc;border-radius:12px;width:300px;display:none;' + | |||
'box-shadow:0 6px 14px rgba(0,0,0,0.25);z-index:10000;'; | |||
var html = ''; | |||
// Language selector | |||
html += '<label style="font-weight:bold;">Select Language</label><br>'; | |||
html += '<select id="pageLang" style="width:100%;padding:6px;margin:6px 0;border:1px solid #ccc;border-radius:5px;">'; | |||
html += '<option value="en">English</option>'; | |||
html += '<option value="hi">Hindi</option>'; | |||
html += '<option value="ta">Tamil</option>'; | |||
html += '</select>'; | |||
// Page creation | |||
html += '<label style="font-weight:bold;">Create new page</label>'; | |||
html += '<input type="text" id="newPageName" placeholder="Enter page name"' + | |||
'style="width:100%;padding:6px;margin-top:4px;border:1px solid #ccc;border-radius:5px;">'; | |||
html += '<button id="createPageBtn" style="margin-top:8px;width:100%;background:#0078D7;' + | |||
'color:white;border:none;padding:6px;border-radius:6px;cursor:pointer;">Create Page</button>'; | |||
// ---------- CATEGORY CREATION (Editor/Admin only) ---------- | |||
if (isEditor || isAdmin) { | |||
html += '<hr style="margin:10px 0;">'; | |||
html += '<label style="font-weight:bold;">Create new category</label>'; | |||
html += '<input type="text" id="newCategoryName" placeholder="Enter category name"' + | |||
'style="width:100%;padding:6px;margin-top:4px;border:1px solid #ccc;border-radius:5px;">'; | |||
html += '<button id="createCategoryBtn" style="margin-top:8px;width:100%;background:#28a745;' + | |||
'color:white;border:none;padding:6px;border-radius:6px;cursor:pointer;">Create Category</button>'; | |||
} | |||
// Dashboard + Mapping | |||
html += '<hr style="margin:10px 0;">'; | |||
html += '<button id="openDashboardBtn" style="width:100%;background:#6f42c1;color:white;' + | |||
'border:none;padding:6px;border-radius:6px;cursor:pointer;">Dashboard</button>'; | |||
html += '<button id="openMappingBtn" style="width:100%;background:#ff5733;color:white;' + | |||
'border:none;padding:6px;border-radius:6px;margin-top:6px;cursor:pointer;">Mapping</button>'; | |||
popup.innerHTML = html; | |||
document.body.appendChild(popup); | |||
// Toggle popup | |||
btn.onclick = function () { | |||
popup.style.display = (popup.style.display === 'none') ? 'block' : 'none'; | |||
}; | |||
// Popup click handler | |||
popup.onclick = function (ev) { | |||
var target = ev.target; | |||
// Create normal page | |||
if (target.id === 'createPageBtn') { | |||
var lang = document.getElementById('pageLang').value; | |||
var name = document.getElementById('newPageName').value.trim(); | |||
if (!name) return alert("Please enter a page name"); | |||
var t = name.replace(/\s+/g, '_'); | |||
var url = (lang === 'hi') ? '/Hi/' + t : | |||
(lang === 'ta') ? '/Ta/' + t : | |||
'/' + t; | |||
// No categories needed here; creating new page directly | |||
window.location.href = url + '?action=edit&veaction=edit'; | |||
} | |||
// Create category | |||
if (target.id === 'createCategoryBtn') { | |||
var cat = document.getElementById('newCategoryName').value.trim(); | |||
if (!cat) return alert("Please enter a category name"); | |||
var cTitle = 'Category:' + cat.replace(/\s+/g, '-'); | |||
window.location.href = '/' + cTitle + '?action=edit&veaction=edit'; | |||
} | |||
if (target.id === 'openDashboardBtn') | |||
window.location.href = '/Special:TeamDashboard'; | |||
if (target.id === 'openMappingBtn') | |||
window.location.href = '/Special:TeamMapping'; | |||
}; | |||
} | |||
if (document.readyState === 'complete' || document.readyState === 'interactive') | |||
initCreateButton(); | |||
else | |||
document.addEventListener('DOMContentLoaded', initCreateButton); | |||
// =================================================================== | |||
// B) CATEGORY + PARENT PAGE LOGIC (Original + Multilingual Enhancements) | |||
// including category inheritance helpers | |||
// =================================================================== | |||
mw.loader.using(['mediawiki.util', 'mediawiki.api']).then(function () { | |||
var api = new mw.Api(); | |||
var user = mw.config.get('wgUserName'); | |||
if (!user) return; | |||
var ns = mw.config.get('wgNamespaceNumber'); | |||
var title = mw.config.get('wgTitle') || ''; | |||
var pagenm = mw.config.get('wgPageName') || ''; | |||
var action = mw.config.get('wgAction') || ''; | |||
var revId = mw.config.get('wgCurRevisionId') || 0; | |||
var urlParams = new URLSearchParams(location.search); | |||
var autoCat = urlParams.get('autocategory'); | |||
var skipParent = urlParams.get('skipparent'); | |||
// --------------------------------------------------------------- | |||
// CATEGORY PROMPT (UNCHANGED) | |||
// --------------------------------------------------------------- | |||
if (ns === 14) { // Category namespace | |||
var key = "category_prompt_done_" + title; | |||
function askCategory() { | |||
if (sessionStorage.getItem(key)) return; | |||
sessionStorage.setItem(key, 'done'); | |||
var ask = confirm( | |||
'You created category "' + title + '".\n\nCreate a page with this name?' | |||
); | |||
if (ask) { | |||
window.location.href = mw.util.getUrl(title, { | |||
action:'edit', veaction:'edit', | |||
autocategory:title, | |||
skipparent:1 | |||
}); | |||
} | |||
} | |||
mw.hook('postEdit').add(askCategory); | |||
$(window).on('popstate', function () { | |||
if (action !== 'view') return; | |||
if (sessionStorage.getItem(key)) return; | |||
api.get({ | |||
action:'query', | |||
titles:'Category:' + title, | |||
prop:'info' | |||
}).done(function (res) { | |||
var page = res.query.pages[Object.keys(res.query.pages)[0]]; | |||
if (page && !page.missing) askCategory(); | |||
}); | |||
}); | |||
} | |||
// --------------------------------------------------------------- | |||
// INJECT AUTOCATEGORY | |||
// --------------------------------------------------------------- | |||
function injectCategory(cat) { | |||
var box = $('#wpTextbox1'); | |||
if (!box.length) return; | |||
var tag = '[[Category:' + cat + ']]'; | |||
if (box.val().indexOf(tag) === -1) | |||
box.val(tag + '\n\n' + box.val()); | |||
} | |||
if (autoCat) { | |||
$(function () { injectCategory(autoCat); }); | |||
mw.hook('ve.activationComplete').add(function () { injectCategory(autoCat); }); | |||
} | |||
// --------------------------------------------------------------- | |||
// LANGUAGE DETECTION | |||
// --------------------------------------------------------------- | |||
var isHindi = pagenm.indexOf("Hi/") === 0; | |||
var isTamil = pagenm.indexOf("Ta/") === 0; | |||
var langPrefix = isHindi ? "Hi/" : (isTamil ? "Ta/" : ""); | |||
// Remove prefix from title | |||
function stripPrefix(t) { | |||
return t.replace(/^Hi\//, "").replace(/^Ta\//, ""); | |||
} | |||
// Build clean title | |||
function buildTitle(parent, child) { | |||
parent = stripPrefix(parent).replace(/^\/+|\/+$/g, ""); | |||
child = stripPrefix(child ).replace(/^\/+|\/+$/g, ""); | |||
if (!parent) | |||
return langPrefix + child; | |||
return langPrefix + parent + "/" + child; | |||
} | |||
// --------------------------------------------------------------- | |||
// HELPER: Get parent categories (exposed globally) | |||
// --------------------------------------------------------------- | |||
window.getParentCategories = function (parentTitle, callback) { | |||
if (!parentTitle) { | |||
callback([]); | |||
return; | |||
} | |||
api.get({ | |||
action: "query", | |||
prop: "categories", | |||
titles: parentTitle, | |||
cllimit: "max" | |||
}).done(function (res) { | |||
try { | |||
var page = res.query.pages[Object.keys(res.query.pages)[0]]; | |||
var cats = []; | |||
if (page && page.categories) { | |||
for (var i = 0; i < page.categories.length; i++) { | |||
var c = page.categories[i].title.replace(/^Category:/, ''); | |||
cats.push(c); | |||
} | |||
} | |||
callback(cats); | |||
} catch (ee) { | |||
callback([]); | |||
} | |||
}).fail(function () { | |||
callback([]); | |||
}); | |||
}; | |||
// --------------------------------------------------------------- | |||
// HELPER: Insert categories into editor (VE or Source) | |||
// Runs once per stored inheritCategories session value | |||
// --------------------------------------------------------------- | |||
function applyInheritedCategories() { | |||
var data = sessionStorage.getItem("inheritCategories"); | |||
if (!data) return; | |||
try { | |||
var cats = JSON.parse(data); | |||
} catch (e) { | |||
sessionStorage.removeItem("inheritCategories"); | |||
return; | |||
} | |||
if (!cats || !cats.length) { | |||
sessionStorage.removeItem("inheritCategories"); | |||
return; | |||
} | |||
// Remove to avoid double-insert | |||
sessionStorage.removeItem("inheritCategories"); | |||
// Build wikitext fragment | |||
var wikitext = ""; | |||
for (var i = 0; i < cats.length; i++) { | |||
wikitext += "[[Category:" + cats[i] + "]]\n"; | |||
} | |||
// 1) If source editor present, insert there | |||
var box = document.getElementById("wpTextbox1"); | |||
if (box) { | |||
box.value = wikitext + "\n" + box.value; | |||
return; | |||
} | |||
// 2) VisualEditor - try to insert using VE model | |||
try { | |||
if (window.ve && ve.init && ve.init.target) { | |||
var surface = ve.init.target.getSurface(); | |||
if (!surface || !surface.getModel) { | |||
// VE not ready yet: retry shortly | |||
setTimeout(applyInheritedCategories, 200); | |||
return; | |||
} | |||
var model = surface.getModel(); | |||
// Attempt insertion; wrap in try/catch to avoid breaking if APIs differ | |||
try { | |||
model.getDocument().transact(model.insertContent(wikitext + "\n")); | |||
} catch (e) { | |||
// As a fallback, try to insert by collapsing selection and inserting text | |||
try { | |||
var frag = model.getDocument().getFragment(); | |||
if (frag && frag.collapseToEnd) { | |||
frag.collapseToEnd(); | |||
frag.insertContent("\n" + wikitext); | |||
} | |||
} catch (e2) { | |||
// give up silently - categories already removed from session | |||
} | |||
} | |||
} | |||
} catch (ee) { | |||
// nothing | |||
} | |||
} | |||
// Run at DOM ready (source editor case) | |||
$(applyInheritedCategories); | |||
// Run after VE activation (VE case) | |||
mw.hook('ve.activationComplete').add(applyInheritedCategories); | |||
// --------------------------------------------------------------- | |||
// PARENT PAGE MODAL (made global so VE can call it: window.askParent) | |||
// + Cancel and click-outside behavior | |||
// --------------------------------------------------------------- | |||
window.askParent = function (child, done) { | |||
if (window.__parentModalShown) return; | |||
window.__parentModalShown = true; | |||
var o = $('<div>').css({ | |||
position:'fixed',top:0,left:0,width:'100%',height:'100%', | |||
background:'rgba(0,0,0,0.5)',display:'flex', | |||
justifyContent:'center',alignItems:'center', | |||
zIndex:999999 | |||
}); | |||
var box = $('<div>').css({ | |||
background:'#fff',padding:'20px',width:'420px', | |||
borderRadius:'10px',boxShadow:'0 4px 12px rgba(0,0,0,0.3)', | |||
position:'relative' | |||
}); | |||
var input = $('<input type="text" placeholder="Search parent...">').css({ | |||
width:'100%',padding:'8px',border:'1px solid #ccc',borderRadius:'6px' | |||
}); | |||
var list = $('<ul>').css({ | |||
listStyle:'none',padding:0,margin:'10px 0 0', | |||
maxHeight:'200px',overflowY:'auto',border:'1px solid #ddd', | |||
borderRadius:'6px',display:'none' | |||
}); | |||
var btns = $('<div>').css({ marginTop:'10px', textAlign:'right' }); | |||
var ok = $('<button>Confirm</button>').css({ | |||
background:'#007bff',color:'#fff',padding:'8px 14px', | |||
border:'none',borderRadius:'6px',cursor:'pointer' | |||
}); | |||
var skip = $('<button>No Parent</button>').css({ | |||
background:'#6c757d',color:'#fff',padding:'8px 14px', | |||
border:'none',borderRadius:'6px',cursor:'pointer', | |||
marginLeft:'8px' | |||
}); | |||
var cancel = $('<button>Cancel</button>').css({ | |||
background:'#dc3545',color:'#fff',padding:'8px 14px', | |||
border:'none',borderRadius:'6px',cursor:'pointer', | |||
marginLeft:'8px' | |||
}); | |||
btns.append(ok, skip, cancel); | |||
box.append('<h3>Select Parent Page</h3>', input, list, btns); | |||
o.append(box); | |||
$('body').append(o); | |||
var selected = ''; | |||
input.on('input', function () { | |||
var q = $.trim(input.val()); | |||
list.empty(); | |||
if (!q) return list.hide(); | |||
api.get({ | |||
action:'opensearch', | |||
search: langPrefix + q, | |||
limit: 12, | |||
namespace:0 | |||
}).done(function (res) { | |||
var results = res[1] || []; | |||
list.empty(); | |||
if (!results.length) return list.hide(); | |||
list.show(); | |||
for (var i = 0; i < results.length; i++) { | |||
(function(pg){ | |||
var li = $('<li>').text(pg).css({ | |||
padding:'8px',cursor:'pointer' | |||
}).hover( | |||
function(){ $(this).css('background','#eee'); }, | |||
function(){ $(this).css('background',''); } | |||
).click(function(){ | |||
selected = pg; | |||
input.val(stripPrefix(pg)); | |||
list.hide(); | |||
}); | |||
list.append(li); | |||
})(results[i]); | |||
} | |||
}); | |||
}); | |||
// Confirm | |||
ok.on('click', function () { | |||
var parent = selected || input.val().trim(); | |||
if (!parent) return alert("Select parent or click No Parent"); | |||
o.remove(); | |||
window.__parentModalShown = false; | |||
done(parent); | |||
}); | |||
// No Parent (explicitly remove parent) | |||
skip.on('click', function () { | |||
o.remove(); | |||
window.__parentModalShown = false; | |||
done(""); | |||
}); | |||
// Cancel (do nothing) | |||
cancel.on('click', function () { | |||
o.remove(); | |||
window.__parentModalShown = false; | |||
done(null); | |||
}); | |||
// Click outside to close (acts like Cancel) | |||
o.on('click', function (e) { | |||
if (e.target === o[0]) { | |||
o.remove(); | |||
window.__parentModalShown = false; | |||
done(null); | |||
} | |||
}); | |||
}; | |||
// --------------------------------------------------------------- | |||
// NEW PAGE HANDLING (when creating a new page via edit) | |||
// — now stores parent categories into sessionStorage before redirect | |||
// --------------------------------------------------------------- | |||
function handleNewPage() { | |||
if (ns !== 0) return; | |||
if (action !== 'edit') return; | |||
if (revId !== 0) return; | |||
if (skipParent && autoCat) return; | |||
askParent(pagenm, function (parent) { | |||
// If user cancelled modal, parent === null | |||
if (parent === null) return; | |||
var finalTitle = buildTitle(parent, pagenm); | |||
// If parent given, fetch its categories and store for the new page | |||
if (parent) { | |||
window.getParentCategories(parent, function(cats) { | |||
try { | |||
sessionStorage.setItem("inheritCategories", JSON.stringify(cats || [])); | |||
} catch (e) {} | |||
// Redirect to edit (VE) | |||
var url = mw.util.getUrl(finalTitle, { veaction:'edit' }); | |||
if (location.href !== url) location.href = url; | |||
}); | |||
} else { | |||
// Explicit "No Parent" — clear parent, but still open editor | |||
try { sessionStorage.removeItem("inheritCategories"); } catch (e) {} | |||
var url2 = mw.util.getUrl(finalTitle, { veaction:'edit' }); | |||
if (location.href !== url2) location.href = url2; | |||
} | |||
}); | |||
} | |||
if (action === 'edit') handleNewPage(); | |||
mw.hook('ve.activationComplete').add(handleNewPage); | |||
}); // end mw.loader.using for Section B | |||
// =================================================================== | |||
// C) CHANGE PARENT PAGE (VE PAGE SETTINGS ONLY) — CLEAN FINAL VERSION | |||
// =================================================================== | |||
mw.loader.using(['mediawiki.util', 'mediawiki.api']).then(function () { | |||
if (window.__changeParentInjected) return; | |||
window.__changeParentInjected = true; | |||
var ns = mw.config.get('wgNamespaceNumber'); | |||
var action = mw.config.get('wgAction') || ""; | |||
var revId = mw.config.get('wgCurRevisionId') || 0; | |||
// only main namespace & editing | |||
if (ns !== 0) return; | |||
var isVE = location.search.indexOf("veaction=edit") !== -1; | |||
var isEdit = action === "edit"; | |||
if (!isVE && !isEdit) return; | |||
if (!revId) return; | |||
var groups = mw.config.get("wgUserGroups") || []; | |||
var allowed = | |||
groups.indexOf("writer") !== -1 || | |||
groups.indexOf("lead") !== -1 || | |||
groups.indexOf("editor") !== -1 || | |||
groups.indexOf("sysop") !== -1; | |||
if (!allowed) return; | |||
// Use a robust wait for VE toolbar + settings popup | |||
function waitAndInject() { | |||
var $settingsTool = $('span.oo-ui-tool-name-settings'); | |||
if (!$settingsTool.length) { | |||
return setTimeout(waitAndInject, 200); | |||
} | |||
var $toolGroup = $settingsTool.closest(".oo-ui-toolGroup-tools"); | |||
if (!$toolGroup.length) { | |||
return setTimeout(waitAndInject, 200); | |||
} | |||
if ($("#ve-change-parent-tool").length) return; | |||
var $tool = $('<span>') | |||
.attr("id", "ve-change-parent-tool") | |||
.addClass("oo-ui-widget oo-ui-widget-enabled oo-ui-tool oo-ui-tool-link") | |||
.css({ display: "block" }); | |||
var $link = $('<a>') | |||
.addClass("oo-ui-tool-link") | |||
.attr("tabindex", "0") | |||
.css({ | |||
display: "flex", | |||
alignItems: "center", | |||
padding: "6px" | |||
}); | |||
// Use hierarchy icon to avoid overlap problems | |||
var $icon = $('<span>') | |||
.addClass("oo-ui-iconElement-icon oo-ui-icon-hierarchy") | |||
.css({ marginRight: "6px" }); | |||
var $label = $('<span>') | |||
.addClass("oo-ui-tool-title") | |||
.text("Change Parent"); | |||
$link.append($icon).append($label); | |||
$tool.append($link); | |||
$toolGroup.append($tool); | |||
// OOUI-safe click | |||
$link.on("click", function (e) { | |||
e.preventDefault(); | |||
e.stopPropagation(); | |||
if (typeof window.askParent !== "function") { | |||
alert("Parent selector not ready. Reload editor."); | |||
return; | |||
} | |||
var fullTitle = mw.config.get("wgPageName") || ""; | |||
// Detect language prefix | |||
var prefix = ""; | |||
if (fullTitle.indexOf("Hi/") === 0) prefix = "Hi/"; | |||
if (fullTitle.indexOf("Ta/") === 0) prefix = "Ta/"; | |||
var noPrefix = fullTitle.replace(/^Hi\//, "").replace(/^Ta\//, ""); | |||
var parts = noPrefix.split("/"); | |||
var child = parts[parts.length - 1]; | |||
window.askParent(fullTitle, function (parent) { | |||
// parent === null means cancel | |||
if (parent === null) return; | |||
parent = parent.replace(/^Hi\//, "") | |||
.replace(/^Ta\//, "") | |||
.trim(); | |||
var newTitle = parent ? | |||
prefix + parent + "/" + child : | |||
prefix + child; | |||
newTitle = newTitle.replace(/\/+/g, "/"); | |||
if (newTitle === fullTitle) { | |||
alert("Parent not changed."); | |||
return; | |||
} | |||
// Before moving, fetch parent categories for the new parent path | |||
var parentForCats = parent || ""; | |||
if (parentForCats) { | |||
// store categories so new page gets them after move+redirect | |||
window.getParentCategories(parentForCats, function (cats) { | |||
try { | |||
sessionStorage.setItem("inheritCategories", JSON.stringify(cats || [])); | |||
} catch (e) {} | |||
// perform move | |||
movePage(fullTitle, newTitle); | |||
}); | |||
} else { | |||
// No parent selected (move to top-level) | |||
try { sessionStorage.removeItem("inheritCategories"); } catch (e) {} | |||
movePage(fullTitle, newTitle); | |||
} | |||
}); | |||
}); | |||
console.log("✔ Change Parent added to VE Page Settings"); | |||
} | |||
// Wait for VE activation and then try injecting (more robust) | |||
mw.hook('ve.activationComplete').add(function () { | |||
waitAndInject(); | |||
}); | |||
// Also attempt immediately (in case VE already activated) | |||
setTimeout(waitAndInject, 300); | |||
// =================================================================== | |||
// MOVE PAGE (unchanged except parent-cats handling is done before calling movePage) | |||
// =================================================================== | |||
function movePage(oldTitle, newTitle) { | |||
var api = new mw.Api(); | |||
api.postWithToken("csrf", { | |||
action: "move", | |||
from: oldTitle, | |||
to: newTitle, | |||
reason: "Updated parent structure", | |||
movetalk: 1, | |||
noredirect: 1 | |||
}) | |||
.done(function () { | |||
api.postWithToken("csrf", { | |||
action: "delete", | |||
title: oldTitle, | |||
reason: "Old parent path cleaned after move" | |||
}) | |||
.done(function () { | |||
alert("Parent updated and old page deleted."); | |||
// After move & delete we redirect to new title. | |||
// applyInheritedCategories() will run on load because we pre-saved inheritCategories. | |||
window.location.href = mw.util.getUrl(newTitle); | |||
}) | |||
.fail(function (err) { | |||
alert("Page moved, but deletion failed: " + | |||
(err && err.error && err.error.info ? err.error.info : "Unknown")); | |||
window.location.href = mw.util.getUrl(newTitle); | |||
}); | |||
}) | |||
.fail(function (err) { | |||
var msg = (err && err.error && err.error.info) ? err.error.info : "Unknown error"; | |||
alert("Move failed: " + msg); | |||
}); | |||
} | |||
}); // end Section C | |||
})(); | |||
mw.loader.using(['jquery'], function () { | |||
$(function () { | |||
// Run only on Special:SpecialPages | |||
if (mw.config.get('wgCanonicalSpecialPageName') !== 'SpecialPages') { | |||
return; | |||
} | |||
var $heading = $('#mw-specialpagesgroup-other'); | |||
if (!$heading.length) { | |||
console.warn('Other special pages heading not found'); | |||
return; | |||
} | |||
var $list = $heading.next('.mw-specialpages-list'); | |||
if (!$list.length) { | |||
console.warn('Other special pages list not found'); | |||
return; | |||
} | |||
// Correct container for Chameleon & all modern skins | |||
var $container = $('.mw-parser-output').first(); | |||
if (!$container.length) { | |||
console.warn('Parser output container not found'); | |||
return; | |||
} | |||
// Move heading + list to top | |||
$heading.add($list).prependTo($container); | |||
}); | |||
}); | |||
Revision as of 16:09, 17 December 2025
mw.loader.using('mediawiki.util').done(function () {
console.log("Common.js loaded safely");
});
/* Any JavaScript here will be loaded for all users on every page load. */
/*
document.addEventListener("DOMContentLoaded", function() {
const btn = document.querySelector(".toggle-btn");
const content = document.querySelector(".toggle-content");
if (btn && content) {
btn.addEventListener("click", function() {
content.style.display = (content.style.display === "block") ? "none" : "block";
});
}
});
// Auto-add parent category when editing/creating a subpage
( function () {
if ( mw.config.get('wgAction') !== 'edit' ) return;
// wgTitle contains title without namespace, e.g. "Ancient-education/Subpage"
var title = mw.config.get('wgTitle') || '';
if ( title.indexOf('/') === -1 ) return; // not a subpage
var parent = title.split('/')[0]; // "Ancient-education"
// jQuery available
$( function () {
var textarea = $('#wpTextbox1');
if ( !textarea.length ) return;
// Only append if not present
var current = textarea.val() || '';
var catTag = '\n[[Category:' + parent + ']]';
if ( current.indexOf(catTag.trim()) === -1 ) {
// Insert the category at the end of the text area (preserve existing text)
textarea.val(current + catTag);
}
} );
}() );
$(document).ready(function () {
// Skip special pages
if (mw.config.get('wgNamespaceNumber') < 0) return;
var $content = $('#mw-content-text');
// Fetch all page titles from the API (main namespace only)
$.get(mw.util.wikiScript('api'), {
action: 'query',
list: 'allpages',
aplimit: 'max',
format: 'json'
}).done(function (data) {
var titles = data.query.allpages.map(function (p) { return p.title; });
var html = $content.html();
titles.forEach(function (title) {
// Safe regex for whole words
var safeTitle = title.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
var regex = new RegExp('\\b(' + safeTitle + ')\\b', 'g');
html = html.replace(regex, '<a href="/wiki/' + encodeURIComponent(title.replace(/ /g, '_')) + '">$1</a>');
});
$content.html(html);
});
});
$(document).ready(function() {
if (mw.config.get('wgNamespaceNumber') >= 0) { // Only show on normal pages
var pageName = mw.config.get('wgPageName');
var uploadUrl = mw.util.getUrl('Form:UploadVideo', { 'page': pageName });
$('<div style="position:fixed; bottom:20px; right:20px; background:#007bff; color:white; padding:10px; border-radius:5px; cursor:pointer; font-weight:bold;">Upload a Video</div>')
.click(function() {
window.location.href = uploadUrl;
}).appendTo('body');
}
});
*/
/* Start Pushkar Code */
$('img').attr('loading', 'lazy');
mw.loader.using(['jquery'], function () {
// Load Slick dynamically
$.getScript('https://cdn.jsdelivr.net/npm/slick-carousel@1.8.1/slick/slick.min.js')
.done(function () {
$(function () {
// Initialize Slick after content loads
$('.adsBannerSliderHero ').not('.slick-initialized').slick({
slidesToShow: 1,
slidesToScroll: 1,
autoplay: true,
autoplaySpeed: 3000,
dots: false,
arrows: true,
prevArrow: '<button class="prev-arrow"><i class="fas fa-chevron-left"></i></button>',
nextArrow: '<button class="next-arrow"><i class="fas fa-chevron-right"></i></button>',
//adaptiveHeight: true
});
$('.sanatanWisdomSlider').not('.slick-initialized').slick({
slidesToShow: 3,
slidesToScroll: 1,
autoplay: true,
autoplaySpeed: 3000,
dots: false,
arrows: true,
prevArrow: '<button class="prev-arrow"><i class="fas fa-chevron-left"></i></button>',
nextArrow: '<button class="next-arrow"><i class="fas fa-chevron-right"></i></button>',
responsive: [
{breakpoint: 768, settings: {arrows: false, centerMode: true, slidesToShow: 2 }},
{breakpoint: 480, settings: { arrows: false,slidesToShow: 1}}
],
adaptiveHeight: true
});
$('.latestArticlesSlider').not('.slick-initialized').slick({
slidesToShow: 3,
slidesToScroll: 1,
autoplay: true,
autoplaySpeed: 3000,
dots: false,
arrows: false,
variableWidth: true,
prevArrow: '<button class="prev-arrow"><i class="fas fa-chevron-left"></i></button>',
nextArrow: '<button class="next-arrow"><i class="fas fa-chevron-right"></i></button>',
responsive: [
{breakpoint: 768, settings: {arrows: false, centerMode: true, slidesToShow: 2 }},
{breakpoint: 480, settings: { arrows: false,slidesToShow: 1}}
],
adaptiveHeight: true
});
});
})
.fail(function () {
console.error('Failed to load Slick Slider JS');
});
});
/* End Pushkar Code */
/* Functions of Slider One */
(function () {
function initSliders() {
var sliders = document.querySelectorAll('.mw-slider');
sliders.forEach(function (slider) {
// Avoid double-init
if (slider._mwSliderInited) return;
slider._mwSliderInited = true;
var viewport = slider.querySelector('.mw-slider-viewport');
var track = slider.querySelector('.mw-slider-track');
var items = Array.from(slider.querySelectorAll('.mw-slider-item'));
var btnPrev = slider.querySelector('.mw-slider-btn.prev');
var btnNext = slider.querySelector('.mw-slider-btn.next');
var currentIndex = 0;
var itemsToShow = getItemsToShow();
var gap = parseFloat(getComputedStyle(track).columnGap || getComputedStyle(track).gap || 16);
function getItemsToShow() {
var w = window.innerWidth;
if (w <= 600) return 1;
if (w <= 900) return 2;
return 3;
}
function updateSizes() {
itemsToShow = getItemsToShow();
// compute single item width including gap
if (!items[0]) return;
var itemRect = items[0].getBoundingClientRect();
gap = parseFloat(getComputedStyle(track).columnGap || getComputedStyle(track).gap || 16);
var single = itemRect.width + gap;
// ensure currentIndex in range
var maxIndex = Math.max(0, items.length - itemsToShow);
currentIndex = Math.min(currentIndex, maxIndex);
// apply transform
var translateX = -currentIndex * single;
track.style.transform = 'translateX(' + translateX + 'px)';
updateButtons();
}
function updateButtons() {
var maxIndex = Math.max(0, items.length - itemsToShow);
if (btnPrev) btnPrev.disabled = currentIndex <= 0;
if (btnNext) btnNext.disabled = currentIndex >= maxIndex;
}
function gotoIndex(index) {
var maxIndex = Math.max(0, items.length - itemsToShow);
currentIndex = Math.max(0, Math.min(maxIndex, index));
updateSizes();
}
if (btnPrev) btnPrev.addEventListener('click', function () {
gotoIndex(currentIndex - 1);
});
if (btnNext) btnNext.addEventListener('click', function () {
gotoIndex(currentIndex + 1);
});
// Keyboard support
slider.addEventListener('keydown', function (e) {
if (e.key === 'ArrowLeft') gotoIndex(currentIndex - 1);
if (e.key === 'ArrowRight') gotoIndex(currentIndex + 1);
});
// Resize handling
var resizeTimeout;
window.addEventListener('resize', function () {
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(updateSizes, 120);
});
// Touch / drag support
(function () {
var startX = 0;
var currentTranslate = 0;
var dragging = false;
var pointerId = null;
function pointerDown(e) {
if (e.pointerType === 'mouse' && e.button !== 0) return;
dragging = true;
pointerId = e.pointerId;
startX = e.clientX;
track.style.transition = 'none';
track.setPointerCapture && track.setPointerCapture(pointerId);
}
function pointerMove(e) {
if (!dragging || e.pointerId !== pointerId) return;
var dx = e.clientX - startX;
var itemRect = items[0] && items[0].getBoundingClientRect();
var single = itemRect ? (itemRect.width + gap) : 0;
var baseTranslate = -currentIndex * single;
track.style.transform = 'translateX(' + (baseTranslate + dx) + 'px)';
}
function pointerUp(e) {
if (!dragging || e.pointerId !== pointerId) return;
dragging = false;
track.style.transition = '';
var dx = e.clientX - startX;
var threshold = Math.max(40, (items[0] ? items[0].getBoundingClientRect().width : 200) * 0.15);
if (dx > threshold) {
gotoIndex(currentIndex - 1);
} else if (dx < -threshold) {
gotoIndex(currentIndex + 1);
} else {
updateSizes();
}
try { track.releasePointerCapture(pointerId); } catch (err) {}
pointerId = null;
}
track.addEventListener('pointerdown', pointerDown);
window.addEventListener('pointermove', pointerMove);
window.addEventListener('pointerup', pointerUp);
track.addEventListener('pointercancel', pointerUp);
})();
// Initial sizes
setTimeout(updateSizes, 60);
});
}
// init on DOM ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initSliders);
} else {
initSliders();
}
// If new content is loaded via AJAX on the wiki, re-init
if (window.mw && mw.hook) {
mw.hook('wikipage.content').add(initSliders);
}
})();
/* Full width Slider */
(function () {
function initFullSliders() {
var sliders = document.querySelectorAll('.mw-fullslider');
sliders.forEach(function (slider) {
if (slider._fullSliderInit) return;
slider._fullSliderInit = true;
var track = slider.querySelector('.mw-fullslider-track');
var slides = Array.from(track.children);
var btnPrev = slider.querySelector('.mw-fullslider-btn.prev');
var btnNext = slider.querySelector('.mw-fullslider-btn.next');
var dots = Array.from(slider.querySelectorAll('.mw-fullslider-dots .dot'));
var current = 0;
var slideCount = slides.length;
function goTo(index, animate) {
current = (index % slideCount + slideCount) % slideCount;
var x = -current * slider.clientWidth;
if (animate === false) track.style.transition = 'none';
else track.style.transition = '';
track.style.transform = 'translateX(' + x + 'px)';
updateControls();
if (animate === false) {
// force reflow then restore
void track.offsetWidth;
track.style.transition = '';
}
}
function updateControls() {
if (btnPrev) btnPrev.disabled = false;
if (btnNext) btnNext.disabled = false;
// update dots
dots.forEach(function (d) {
d.classList.toggle('active', +d.getAttribute('data-index') === current);
d.setAttribute('aria-pressed', (+d.getAttribute('data-index') === current).toString());
});
}
if (btnPrev) btnPrev.addEventListener('click', function () { goTo(current - 1); });
if (btnNext) btnNext.addEventListener('click', function () { goTo(current + 1); });
dots.forEach(function (dot) {
dot.addEventListener('click', function () {
var idx = parseInt(this.getAttribute('data-index'), 10);
goTo(idx);
});
});
// Resize handling: ensure slide width matches viewport
var resizeTimer;
function onResize() {
clearTimeout(resizeTimer);
resizeTimer = setTimeout(function () {
// Recompute translate in case width changed
goTo(current, false);
}, 80);
}
window.addEventListener('resize', onResize);
// Touch / drag support
(function () {
var startX = 0, startTranslate = 0, dragging = false, pointerId = null;
function down(e) {
if (e.pointerType === 'mouse' && e.button !== 0) return;
dragging = true;
pointerId = e.pointerId;
startX = e.clientX;
var style = window.getComputedStyle(track);
var matrix = new WebKitCSSMatrix(style.transform);
startTranslate = matrix.m41 || 0;
track.style.transition = 'none';
e.target.setPointerCapture && e.target.setPointerCapture(pointerId);
}
function move(e) {
if (!dragging || e.pointerId !== pointerId) return;
var dx = e.clientX - startX;
track.style.transform = 'translateX(' + (startTranslate + dx) + 'px)';
}
function up(e) {
if (!dragging || e.pointerId !== pointerId) return;
dragging = false;
track.style.transition = '';
var dx = e.clientX - startX;
var threshold = Math.max(40, slider.clientWidth * 0.12);
if (dx > threshold) goTo(current - 1);
else if (dx < -threshold) goTo(current + 1);
else goTo(current);
try { e.target.releasePointerCapture(pointerId); } catch (err) {}
pointerId = null;
}
track.addEventListener('pointerdown', down);
window.addEventListener('pointermove', move);
window.addEventListener('pointerup', up);
track.addEventListener('pointercancel', up);
})();
// Autoplay (optional): change interval or set autoplay = false
var autoplay = true;
var autoplayInterval = 4500; // ms
var autoplayTimer = null;
function startAutoplay() {
if (!autoplay) return;
stopAutoplay();
autoplayTimer = setInterval(function () { goTo(current + 1); }, autoplayInterval);
}
function stopAutoplay() {
if (autoplayTimer) { clearInterval(autoplayTimer); autoplayTimer = null; }
}
slider.addEventListener('mouseenter', stopAutoplay);
slider.addEventListener('mouseleave', startAutoplay);
slider.addEventListener('focusin', stopAutoplay);
slider.addEventListener('focusout', startAutoplay);
// Respect prefers-reduced-motion
var rmq = window.matchMedia('(prefers-reduced-motion: reduce)');
if (rmq.matches) autoplay = false;
// Ensure initial sizing: make each slide exactly slider.clientWidth
function layoutSlides() {
var w = slider.clientWidth;
slides.forEach(function (s) { s.style.minWidth = w + 'px'; s.style.maxWidth = w + 'px'; });
goTo(current, false);
}
// Wait for images then layout
function imgsReady(cb) {
var imgs = Array.from(slider.querySelectorAll('img'));
var rem = imgs.length;
if (!rem) return cb();
imgs.forEach(function (img) {
if (img.complete) { rem--; if (!rem) cb(); }
else {
img.addEventListener('load', function () { rem--; if (!rem) cb(); });
img.addEventListener('error', function () { rem--; if (!rem) cb(); });
}
});
}
imgsReady(function () {
layoutSlides();
startAutoplay();
window.addEventListener('resize', onResize);
});
// expose for debug (optional)
slider._goTo = goTo;
});
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', initFullSliders);
else initFullSliders();
if (window.mw && mw.hook) mw.hook('wikipage.content').add(initFullSliders);
})();
// --- Disable Tools Option for all users except admin ---
/* ============================================================
Hide Tools menu for all non-admin users (Chameleon skin)
Show Tools normally for sysop users.
100% reliable version
============================================================ */
mw.loader.using(['mediawiki.user'], function () {
mw.user.getGroups().then(function (groups) {
var isAdmin = groups.indexOf('sysop') !== -1;
// Add CSS class to <body> for non-admin
if (!isAdmin) {
document.documentElement.classList.add('hide-tools');
return;
}
// Admin: ensure Tools menu is visible
// Chameleon sometimes loads menu late → use multiple attempts
function showTools() {
// Remove hide class completely
document.documentElement.classList.remove('hide-tools');
// Explicitly show Tools containers
var dropdown = document.querySelector('.p-tb-dropdown');
if (dropdown) dropdown.style.display = '';
var toggleLink = document.querySelector('.p-tb-toggle');
if (toggleLink) toggleLink.parentElement.style.display = '';
}
// Try repeatedly to bypass lazy loading
showTools();
setTimeout(showTools, 200);
setTimeout(showTools, 600);
setTimeout(showTools, 1000);
setTimeout(showTools, 1500);
});
});
// == Showing Template as default footer
$(function () {
// Hindi detection
var parts = window.location.pathname.split('/').filter(Boolean);
var isHindi = parts.length > 0 && parts[0].toLowerCase() === 'hi';
var template = isHindi ? 'Custom-footer-Hindi' : 'Custom-footer';
// Watcher for footer DOM creation
const observer = new MutationObserver(function () {
// When the footer block is PRESENT and FULLY BUILT
if ($('.footercontainer.container #footer-info').length &&
$('.footercontainer.container #footer-places').length) {
// Remove default footer, icons, extra rows
$('.footercontainer.container').remove();
// Add your custom footer AFTER Chameleon block
$.ajax({
url: '/index.php?title=Template:' + template + '&action=render',
type: 'GET',
success: function (data) {
// Avoid duplicates
if (!$('#mw-custom-footer').length) {
$('<div id="mw-custom-footer"></div>')
.html(data)
.appendTo('body');
}
}
});
// Stop watching
observer.disconnect();
}
});
// Now actually watch the body for late additions
observer.observe(document.body, {
childList: true,
subtree: true
});
});
// Related_links button mobile
$(document).ready(function () {
var box = $('.related-links-box');
if (box.length) {
$('body').on('click', '.related-links-box h2', function () {
box.toggleClass('open');
});
}
});
//Category list show more button
$(document).ready(function () {
$('.show-all-categories').on('click', function () {
$('.category-full-list').slideDown(200);
$(this).hide();
});
});
// Latest article Creation UI
$(document).ready(function () {
// Only run on this page
if (mw.config.get("wgPageName") !== "LatestArticlesUpdate") return;
console.log("Latest Articles Editor Loaded (ES5)");
// Load JSON from page
new mw.Api().get({
action: "query",
titles: "LatestArticlesJSON",
prop: "revisions",
rvprop: "content",
formatversion: "2"
}).done(function (res) {
var content = res.query.pages[0].revisions[0].content || "[]";
var data = [];
try {
data = JSON.parse(content);
} catch (e) {}
// Prefill boxes
for (var i = 0; i < data.length; i++) {
$("#title" + (i + 1)).val(data[i].title);
$("#link" + (i + 1)).val(data[i].link);
}
});
// SAVE BUTTON CLICK
$("#saveLatestArticlesBtn").click(function () {
var items = [];
var i, t, l;
for (i = 1; i <= 10; i++) {
t = $("#title" + i).val().trim();
l = $("#link" + i).val().trim();
if (t !== "" && l !== "") {
items.push({ title: t, link: l });
}
}
// Save JSON back to storage page
new mw.Api().postWithEditToken({
action: "edit",
title: "LatestArticlesJSON",
text: JSON.stringify(items, null, 2),
summary: "Updated Latest Articles"
}).done(function () {
$("#saveStatus").html("<b style='color:green;'>✔ Saved successfully!</b>");
});
});
});
// To show updated articles in Frontpage
mw.hook("wikipage.content").add(function () {
// Run only if div exists
if (!document.getElementById("LatestArticlesOutput")) return;
console.log("Homepage Latest Articles Loader Running");
new mw.Api().get({
action: "query",
titles: "LatestArticlesJSON",
prop: "revisions",
rvprop: "content",
formatversion: "2"
}).done(function (res) {
var content = res.query.pages[0].revisions[0].content || "[]";
var data = [];
try { data = JSON.parse(content); } catch(e){}
var out = $("#LatestArticlesOutput");
out.html("");
for (var i = 0; i < data.length; i++) {
var row = data[i];
out.append(
'<a class="latestArticleBtn" href="' + row.link + '">' +
row.title +
'</a>'
);
}
});
});
// Paste this into MediaWiki:Common.js
mw.loader.using('ext.visualEditor.core').then(function () {
// Small helper: detect domain-like text (gives true for "example.com", "sub.example.co", "example.com/path")
function looksLikeDomain(text) {
if (!text) return false;
// allow www., allow path after TLD, basic heuristic
return /\b([a-z0-9-]+\.)+[a-z]{2,}(\/\S*)?/i.test(text.trim());
}
// Normalize a domain-like text into an https:// URL
function buildExternalFromText(text) {
text = (text || '').trim();
if (!text) return null;
// If already has protocol, return as-is
if (/^\w+:\/\//.test(text)) return text;
// Remove leading slashes if VE added them (e.g. "/Writerpage1/gulfplacement.com")
text = text.replace(/^\/+/, '');
// If text contains ' ' (label + url), try to extract domain-like chunk
var match = text.match(/([a-z0-9-]+\.)+[a-z]{2,}(\/\S*)?/i);
if (match) {
return 'https://' + match[0];
}
return 'https://' + text;
}
function ensureExternalAnchorsInSurface(surface) {
if (!surface || !surface.$element) return function () {};
var $el = surface.$element;
var observer = null;
var repeatTimer = null;
var repeatCount = 0;
var MAX_REPEAT = 30; // ~30 * 200ms = 6s of retries
// fix anchors that VE rewrote into internal
function fixAnchorsOnce() {
try {
$el.find('a').each(function () {
var a = this;
var href = a.getAttribute('href') || '';
var text = a.textContent || a.innerText || '';
// If href is relative or missing protocol AND the visible link text looks like a domain
if (href && !/^\w+:\/\//.test(href) && looksLikeDomain(text)) {
var newHref = buildExternalFromText(text);
if (newHref) {
a.setAttribute('href', newHref);
// mark so we don't process again
a.setAttribute('data-external-forced', '1');
// show as external in VE dialog as well by adding rel
a.setAttribute('rel', 'noopener noreferrer');
}
}
// Some VE-created anchors may have href like "/Writerpage1/gulfplacement.com" or "Writerpage1/gulfplacement.com"
// The above logic catches those since href lacks protocol and text contains domain-like string.
});
} catch (e) {
// ignore
console.log('fixAnchorsOnce error', e);
}
}
// Start a short-lived repetition (overrides background rewrite that runs after a delay)
function startRepeater() {
repeatCount = 0;
if (repeatTimer) {
clearInterval(repeatTimer);
}
repeatTimer = setInterval(function () {
fixAnchorsOnce();
repeatCount++;
if (repeatCount >= MAX_REPEAT) {
clearInterval(repeatTimer);
repeatTimer = null;
}
}, 200);
}
// MutationObserver to catch transform events quickly
try {
observer = new MutationObserver(function () {
fixAnchorsOnce();
});
observer.observe($el[0], { childList: true, subtree: true, attributes: true });
} catch (e) {
// MutationObserver not available? fallback to simple interval already used above
console.log('MutationObserver not available', e);
}
// return cleanup function (if needed)
return function cleanup() {
if (observer) {
try { observer.disconnect(); } catch (e) {}
observer = null;
}
if (repeatTimer) {
clearInterval(repeatTimer);
repeatTimer = null;
}
};
}
// Wait for VE to initialize and then attach paste handler and anchor-fixer
ve.init.platform.addReadyCallback(function () {
try {
var surface = ve.init.target.getSurface();
if (!surface || !surface.$element) {
return;
}
// Disable as many auto-internal-link hooks as reasonable client-side (best-effort)
try {
if (ve.ui && ve.ui.MWInternalLinkAnnotation) {
if (ve.ui.MWInternalLinkAnnotation.static) {
ve.ui.MWInternalLinkAnnotation.static.matchFunction = function () { return false; };
}
}
if (ve.dm && ve.dm.MWInternalLinkAnnotation && ve.dm.MWInternalLinkAnnotation.static) {
ve.dm.MWInternalLinkAnnotation.static.matchRdfaTypes = [];
ve.dm.MWInternalLinkAnnotation.static.matchFunction = function () { return false; };
}
if (ve.ce && ve.ce.MWInternalLinkAnnotation && ve.ce.MWInternalLinkAnnotation.static) {
ve.ce.MWInternalLinkAnnotation.static.matchRdfaTypes = [];
}
} catch (err) {
// ignore if internals differ
console.log('VE annotator override error', err);
}
// Attach paste handler
surface.$element.on('paste', function (e) {
try {
var data = (e.originalEvent || e).clipboardData;
if (!data) return;
var text = data.getData('text/plain');
if (!text) return;
// Prevent default paste (we will insert cleaned text)
e.preventDefault();
// Convert any URLs to explicit external link syntax in the pasted text
// This helps VE treat them as external when inserted
text = text.replace(/https?:\/\/[^\s]+/g, function (url) {
return '[' + url + ']';
});
// Insert cleaned text into VE surface model (best-effort simple insertion)
try {
// Use VE model insertion if available
if (surface.model && surface.model.getFragment) {
surface.model.getFragment().insertContent(text);
} else {
// fallback to inserting into contenteditable DOM
document.execCommand('insertText', false, text);
}
} catch (insertErr) {
// fallback DOM insert
document.execCommand('insertText', false, text);
}
// Immediately run anchor fixer and start repeating to defeat delayed rewriter
var cleanup = ensureExternalAnchorsInSurface(surface);
// ensure cleanup after a while (we already limit repeats inside ensureExternalAnchorsInSurface)
setTimeout(function () {
if (typeof cleanup === 'function') {
try { cleanup(); } catch (e) {}
}
}, 8000); // give it up to 8s (safe upper bound) then cleanup
// also start immediate fight-back
// (the internal ensureExternalAnchorsInSurface already schedules repeats)
} catch (outerE) {
console.log('paste handler error', outerE);
}
});
// Extra safety: run an initial pass on load to correct any existing bad anchors
try {
setTimeout(function () {
ensureExternalAnchorsInSurface(surface);
}, 500);
} catch (e) {
// ignore
}
} catch (e) {
console.log('VE addReadyCallback error', e);
}
});
});
//Showing SEO button in Visual Editor
(function () {
mw.loader.using([
'ext.visualEditor.desktopArticleTarget.init',
'oojs-ui',
'jquery',
'mediawiki.api'
]).done(function () {
/* --------------------------------------------------
Add "SEO" button to VisualEditor toolbar
-------------------------------------------------- */
if (mw.config.get('wgIsArticle')) {
mw.hook('ve.activationComplete').add(function () {
try {
var veTarget = ve.init && ve.init.target;
var toolbar = veTarget && veTarget.toolbar;
if (!toolbar) return;
if (toolbar.$element.find('.ve-ui-toolbar-seoButton').length) return;
var seoButton = new OO.ui.ButtonWidget({
label: 'SEO',
icon: 'settings',
classes: ['ve-ui-toolbar-seoButton']
});
seoButton.on('click', function () {
openSEODialog(veTarget);
});
toolbar.$element.find('.oo-ui-toolbar-actions').append(seoButton.$element);
} catch (e) {
console.error('[SEO] Toolbar init error:', e);
}
});
}
/* --------------------------------------------------
SEO Dialog
-------------------------------------------------- */
function openSEODialog(veTarget) {
var surface = veTarget.surface;
var model = surface.getModel();
var docText = '';
try {
docText = model.getDocument().data.getText(true);
} catch (e) {
var txt = document.getElementById('wpTextbox1');
if (txt) docText = txt.value;
}
// Extract existing SEO comment
var oldTitle = '', oldDesc = '', oldKeys = '';
var match = /<!--\s*SEO\s+title=(.*?)\s+description=([\s\S]*?)\s+keywords=([\s\S]*?)\s*-->/i.exec(docText);
if (match) {
oldTitle = match[1].trim();
oldDesc = match[2].trim();
oldKeys = match[3].trim();
}
// Input fields
var titleInput = new OO.ui.TextInputWidget({ placeholder: 'Meta Title', value: oldTitle });
var descInput = new OO.ui.MultilineTextInputWidget({ placeholder: 'Meta Description', rows: 3, value: oldDesc });
var keysInput = new OO.ui.TextInputWidget({ placeholder: 'Meta Keywords', value: oldKeys });
// Create Dialog
var SEODialog = function (cfg) { SEODialog.super.call(this, cfg); };
OO.inheritClass(SEODialog, OO.ui.ProcessDialog);
SEODialog.static.name = 'seoDialog';
SEODialog.static.title = 'SEO Settings';
SEODialog.static.size = 'larger';
SEODialog.static.actions = [
{ action: 'save', label: 'Save', flags: ['primary', 'progressive'] },
{ action: 'cancel', label: 'Cancel', flags: ['safe'] }
];
SEODialog.prototype.initialize = function () {
SEODialog.super.prototype.initialize.apply(this, arguments);
var panel = new OO.ui.PanelLayout({ padded: true, expanded: true });
panel.$element.append(
$('<label>').text('Meta Title'),
titleInput.$element, $('<br><br>'),
$('<label>').text('Meta Description'),
descInput.$element, $('<br><br>'),
$('<label>').text('Meta Keywords'),
keysInput.$element
);
this.$body.append(panel.$element);
};
SEODialog.prototype.getBodyHeight = function () {
return 420;
};
/* --------------------------------------------------
FINAL FIX: SAFE SAVE USING API
(DO NOT CHANGE VE CONTENT)
-------------------------------------------------- */
SEODialog.prototype.getActionProcess = function (action) {
var dialog = this;
if (action === 'save') {
return new OO.ui.Process(function () {
var t = titleInput.getValue().trim();
var d = descInput.getValue().trim();
var k = keysInput.getValue().trim();
// New SEO block
var newSEO =
"<!--SEO title=\"" + mw.html.escape(t) +
"\" description=\"" + mw.html.escape(d) +
"\" keywords=\"" + mw.html.escape(k) +
"\" -->";
var api = new mw.Api();
// STEP 1: Load TRUE source wikitext (not VE text)
api.get({
action: "query",
prop: "revisions",
rvprop: "content",
formatversion: "2",
titles: mw.config.get("wgPageName")
}).done(function (data) {
var page = data.query.pages[0];
var source = page.revisions[0].content;
// STEP 2: Remove old SEO block
source = source.replace(/<!--SEO[\s\S]*?-->/i, "");
// STEP 3: Add new SEO at very top
source = newSEO + "\n" + source;
// STEP 4: Save back safely
api.postWithToken("csrf", {
action: "edit",
title: mw.config.get("wgPageName"),
text: source,
summary: "Updated SEO metadata"
}).done(function () {
mw.notify("✔ SEO updated successfully");
location.reload();
});
});
dialog.close();
});
}
if (action === 'cancel') {
return new OO.ui.Process(function () {
dialog.close();
});
}
};
// --------------------------------------------------
// Open dialog
// --------------------------------------------------
var wm = window._seoWM || new OO.ui.WindowManager();
if (!window._seoWM) {
window._seoWM = wm;
$(document.body).append(wm.$element);
}
var dlg = new SEODialog();
wm.addWindows([dlg]);
wm.openWindow(dlg);
}
});
})();
/* ====================================================================
VISUALEDITOR – Writer Tools (Common.js Edition)
Word Count, Character Count, Reading Time
With Close/Open Button
ES5 Compatible | MediaWiki 1.39.x
==================================================================== */
(function () {
if (window.WT_Loaded) return;
window.WT_Loaded = true;
var PANEL_ID = 'wt-ve-panel';
var WPM = 200; // words per minute reading speed
var updateTimeout = null;
var surfaceRef = null;
/* --------------------------------------------------------------
Create Writer Tools Panel + Open Button
-------------------------------------------------------------- */
function createPanel() {
if (document.getElementById(PANEL_ID)) return;
/* Main Panel */
var panel = document.createElement('div');
panel.id = PANEL_ID;
panel.style.position = 'fixed';
panel.style.left = '20px';
panel.style.bottom = '20px';
panel.style.padding = '12px 16px';
panel.style.fontSize = '13px';
panel.style.background = '#fff';
panel.style.border = '1px solid #ccc';
panel.style.borderRadius = '8px';
panel.style.boxShadow = '0 2px 10px rgba(0,0,0,0.1)';
panel.style.zIndex = '2000';
panel.style.minWidth = '220px';
/* Close Button (X) */
var closeBtn = document.createElement('div');
closeBtn.textContent = '✕';
closeBtn.style.position = 'absolute';
closeBtn.style.top = '4px';
closeBtn.style.right = '8px';
closeBtn.style.cursor = 'pointer';
closeBtn.style.fontSize = '14px';
closeBtn.style.color = '#666';
closeBtn.onclick = function () {
panel.style.display = 'none';
document.getElementById('wt-open-btn').style.display = 'block';
};
panel.appendChild(closeBtn);
/* Title */
var title = document.createElement('div');
title.textContent = 'Writer Tools';
title.style.fontWeight = 'bold';
title.style.marginBottom = '6px';
panel.appendChild(title);
/* Stats */
var wordsDiv = document.createElement('div');
wordsDiv.id = 'wt-words';
wordsDiv.textContent = 'Words: 0';
panel.appendChild(wordsDiv);
var charsDiv = document.createElement('div');
charsDiv.id = 'wt-chars';
charsDiv.textContent = 'Characters: 0';
panel.appendChild(charsDiv);
var rtDiv = document.createElement('div');
rtDiv.id = 'wt-rt';
rtDiv.textContent = 'Reading time: 0 min';
panel.appendChild(rtDiv);
document.body.appendChild(panel);
/* OPEN Button (Floating) */
var openBtn = document.createElement('div');
openBtn.id = 'wt-open-btn';
openBtn.textContent = 'Writer Tools';
openBtn.style.position = 'fixed';
openBtn.style.left = '20px';
openBtn.style.bottom = '20px';
openBtn.style.padding = '6px 10px';
openBtn.style.background = '#0366d6';
openBtn.style.color = '#fff';
openBtn.style.borderRadius = '6px';
openBtn.style.cursor = 'pointer';
openBtn.style.fontSize = '12px';
openBtn.style.zIndex = '2001';
openBtn.style.display = 'none';
openBtn.onclick = function () {
panel.style.display = 'block';
openBtn.style.display = 'none';
};
document.body.appendChild(openBtn);
}
/* --------------------------------------------------------------
Text Utilities
-------------------------------------------------------------- */
function cleanText(str) {
if (!str) return '';
str = str.replace(/\u00A0/g, ' '); // non-breaking spaces
return str.replace(/\s+/g, ' ').trim();
}
function countWords(str) {
if (!str) return 0;
var arr = str.split(/\s+/);
var c = 0;
for (var i = 0; i < arr.length; i++) {
if (/[A-Za-z0-9]/.test(arr[i])) c++;
}
return c;
}
function countChars(str) {
return str ? str.length : 0;
}
/* --------------------------------------------------------------
Update Panel Stats
-------------------------------------------------------------- */
function updateStats() {
if (!surfaceRef) return;
try {
var view = surfaceRef.getView();
if (!view) return;
var text = view.$element.text() || '';
text = cleanText(text);
var words = countWords(text);
var chars = countChars(text);
var minutes = Math.ceil(words / WPM);
if (minutes < 1) minutes = 1;
document.getElementById('wt-words').textContent = 'Words: ' + words;
document.getElementById('wt-chars').textContent = 'Characters: ' + chars;
document.getElementById('wt-rt').textContent = 'Reading time: ' + minutes + ' min';
} catch (err) {
console.error('WriterTools update error:', err);
}
}
/* --------------------------------------------------------------
Attach to VisualEditor surface
-------------------------------------------------------------- */
function attachToVE() {
try {
surfaceRef = null;
if (
ve &&
ve.init &&
ve.init.target &&
ve.init.target.getSurface
) {
surfaceRef = ve.init.target.getSurface();
}
if (!surfaceRef) return;
createPanel();
updateStats();
/* Update while typing (throttle 200ms) */
surfaceRef.getModel().on('transact', function () {
if (updateTimeout) clearTimeout(updateTimeout);
updateTimeout = setTimeout(updateStats, 200);
});
} catch (e) {
console.error('WriterTools attach error:', e);
}
}
/* --------------------------------------------------------------
Trigger when VE activates
-------------------------------------------------------------- */
mw.hook('ve.activationComplete').add(function () {
setTimeout(attachToVE, 400);
});
})();
// Team Dashboard Settings
mw.loader.using(['mediawiki.api', 'mediawiki.util']).then(function () {
(function ($) {
/** ---------- Utility: escape HTML ---------- **/
function escapeHtml(str) {
return String(str)
.replace(/&/g, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}
/** ---------- Detect Allowed Special Pages ---------- **/
var pageName = (mw.config.get('wgCanonicalSpecialPageName') || '').toLowerCase();
var allowedPages = ['teamdashboard', 'approvedpages'];
if (!allowedPages.includes(pageName)) return;
/** ---------- Accordion UI (with LocalStorage) ---------- **/
function initAccordionOnce() {
$('.accordion-toggle').each(function () {
var $el = $(this);
var targetSel = $el.attr('data-target');
var $target = $(targetSel);
if (!$target.length) return;
if ($el.find('.pa-arrow').length === 0) {
$el.prepend('<span class="pa-arrow">▶</span> ');
}
var saved = localStorage.getItem('pa:' + targetSel);
if (saved === 'open') {
$target.show();
$el.addClass('open');
$el.find('.pa-arrow').text('▼ ');
} else {
$target.hide();
$el.removeClass('open');
$el.find('.pa-arrow').text('▶ ');
}
if (!$el.data('pa-bound')) {
$el.on('click', function () {
var isOpen = $el.hasClass('open');
if (isOpen) {
$el.removeClass('open');
$target.slideUp(150);
$el.find('.pa-arrow').text('▶ ');
localStorage.setItem('pa:' + targetSel, 'closed');
} else {
$el.addClass('open');
$target.slideDown(150);
$el.find('.pa-arrow').text('▼ ');
localStorage.setItem('pa:' + targetSel, 'open');
}
});
$el.data('pa-bound', true);
}
});
}
/** ---------- MediaWiki API ---------- **/
var api = new mw.Api();
function getFreshToken() {
return api
.get({
action: 'query',
meta: 'tokens',
type: 'csrf',
format: 'json'
})
.then(function (d) {
return d.query.tokens.csrftoken || '';
})
.catch(function () {
return '';
});
}
/** ---------- POST helper ---------- **/
function postAction(fd) {
return $.ajax({
url: mw.util.getUrl('Special:TeamDashboard'),
type: 'POST',
data: fd,
processData: false,
contentType: false,
headers: { 'X-Requested-With': 'XMLHttpRequest' }
});
}
/** ---------- Approve / Reject ---------- **/
$(document).on('click', 'button[data-action]', function (e) {
e.preventDefault();
var $btn = $(this);
if ($btn.prop('disabled')) return;
var action = $btn.data('action'),
pageId = $btn.data('page-id');
$btn.prop('disabled', true);
getFreshToken().then(function (token) {
if (!token) {
alert('Session expired, reload.');
$btn.prop('disabled', false);
return;
}
var fd = new FormData();
fd.append('pa_action', action);
fd.append('page_id', pageId);
fd.append('token', token);
postAction(fd)
.done(function () {
if (action === 'approve') {
$btn.text('Approved ✓').prop('disabled', true);
$btn.closest('td').prev().prev().text('approved');
} else if (action === 'reject') {
$btn.text('Rejected ✗').prop('disabled', true);
$btn.closest('td').prev().prev().text('rejected');
}
})
.fail(function () {
alert('Action failed. Please try again.');
$btn.prop('disabled', false);
});
});
});
/** ---------- Comment submit ---------- **/
$(document).on('click', '.comment-submit', function (e) {
e.preventDefault();
var $btn = $(this);
if ($btn.prop('disabled')) return;
var $wrap = $btn.closest('.ajax-comment-wrap'),
txt = $.trim($wrap.find('input.comment-input').val() || ''),
pageId = $btn.data('page-id');
if (txt === '') return;
$btn.prop('disabled', true);
getFreshToken().then(function (token) {
if (!token) {
alert('Session expired, reload.');
$btn.prop('disabled', false);
return;
}
var fd = new FormData();
fd.append('pa_action', 'comment');
fd.append('page_id', pageId);
fd.append('token', token);
fd.append('comment', txt);
postAction(fd)
.done(function () {
var $td = $btn.closest('td');
var $list = $td.find('.comment-list');
if (!$list.length) {
var box = $('<div class="comment-box"><b>Comments</b><ul class="comment-list"></ul></div>');
$td.append(box);
$list = box.find('.comment-list');
}
var username = mw.config.get('wgUserName') || 'You';
var now = new Date();
var istOffset = 5.5 * 60 * 60 * 1000;
var ist = new Date(now.getTime() + istOffset);
var stamp =
ist.getFullYear() +
'-' +
String(ist.getMonth() + 1).padStart(2, '0') +
'-' +
String(ist.getDate()).padStart(2, '0') +
' ' +
String(ist.getHours()).padStart(2, '0') +
':' +
String(ist.getMinutes()).padStart(2, '0') +
':' +
String(ist.getSeconds()).padStart(2, '0') +
' IST';
var li = $('<li/>').html(
'<b>' +
escapeHtml(username) +
'</b> <span class="date">(' +
stamp +
')</span><br>' +
escapeHtml(txt) +
' <button class="edit-comment-btn">✏️</button>'
);
$list.prepend(li);
$wrap.find('input.comment-input').val('');
})
.always(function () {
$btn.prop('disabled', false);
});
});
});
/** ---------- Delete comment ---------- **/
$(document).on('click', '.delete-comment-btn', function (e) {
e.preventDefault();
var $btn = $(this);
if ($btn.prop('disabled')) return;
if (!confirm('Delete this comment?')) return;
var commentId = $btn.data('comment-id'),
pageId = $btn.data('page-id');
$btn.prop('disabled', true);
getFreshToken().then(function (token) {
if (!token) {
alert('Session expired.');
$btn.prop('disabled', false);
return;
}
var fd = new FormData();
fd.append('pa_action', 'delete_comment');
fd.append('comment_id', commentId);
fd.append('page_id', pageId);
fd.append('token', token);
postAction(fd)
.done(function () {
$btn.closest('li').fadeOut(200, function () {
$(this).remove();
});
})
.always(function () {
$btn.prop('disabled', false);
});
});
});
/** ---------- FIXED: Edit comment handler ---------- **/
$(document).on('click', '.edit-comment-btn', function () {
var $btn = $(this);
var $li = $btn.closest('li');
var oldText = $li.clone().children().remove().end().text().trim();
var pageId = $li.closest('td').find('.comment-submit').data('page-id');
var commentId = $btn.data('comment-id');
if ($li.find('.edit-comment-area').length) return;
var $area = $('<textarea class="edit-comment-area"></textarea>').val(oldText);
var $save = $('<button class="pa-btn save-edit">Save</button>');
var $cancel = $('<button class="pa-btn danger cancel-edit">Cancel</button>');
$li.append($area, $save, $cancel);
$save.on('click', function () {
var newText = $.trim($area.val());
if (!newText) return alert('Comment cannot be empty.');
getFreshToken().then(function (token) {
var fd = new FormData();
fd.append('pa_action', 'edit_comment');
fd.append('comment_id', commentId);
fd.append('page_id', pageId);
fd.append('comment', newText);
fd.append('token', token);
postAction(fd)
.done(function () {
alert('Comment updated successfully.');
$area.remove();
$save.remove();
$cancel.remove();
$btn.replaceWith(
'<span>' +
escapeHtml(newText) +
' <em>(edited)</em></span>'
);
})
.fail(function () {
alert('Comment updated successfully.');
});
});
});
$cancel.on('click', function () {
$area.remove();
$save.remove();
$cancel.remove();
});
});
/** ---------- Search Filter (Dashboard + ApprovedPages) ---------- **/
$(document).on('input', '.pa-search', function () {
var q = $(this).val().toLowerCase();
$('.approved-table tbody tr, .dashboard-table tbody tr').each(function () {
$(this).toggle($(this).text().toLowerCase().indexOf(q) !== -1);
});
});
/** ---------- EXTRA SEARCH FOR ACCORDION (Leads + Writers) ---------- **/
$(document).on('input', '.pa-search', function () {
var q = $(this).val().toLowerCase();
// 1. Filter approved + pending tables (existing behavior)
$('.approved-table tbody tr, .dashboard-table tbody tr').each(function () {
$(this).toggle($(this).text().toLowerCase().indexOf(q) !== -1);
});
// 2. Filter accordion lead items
$('.accordion-item').each(function () {
var txt = $(this).text().toLowerCase();
if (txt.indexOf(q) !== -1) {
$(this).show();
} else {
$(this).hide();
}
});
});
/** ---------- Sort Approved Pages ---------- **/
$(document).on('change', '.pa-sort', function () {
var mode = $(this).val();
var rows = $('.approved-table tbody tr').get();
rows.sort(function (a, b) {
if (mode === 'az') {
var ta = $(a).find('.title').text().toLowerCase();
var tb = $(b).find('.title').text().toLowerCase();
return ta.localeCompare(tb);
}
if (mode === 'updated') {
var da = new Date($(a).data('updated'));
var db = new Date($(b).data('updated'));
return db - da; // newest first
}
return 0;
});
$.each(rows, function (_, row) {
$('.approved-table tbody').append(row);
});
});
/** ---------- Init ---------- **/
initAccordionOnce();
setTimeout(initAccordionOnce, 700);
})(jQuery);
});
/** ---------- Article Header Sort (Pending Pages) ---------- **/
$(document).on('change', '.pa-article-sort', function () {
var mode = $(this).val();
var $table = $(this).closest('table');
var $tbody = $table.find('tbody');
if (!$tbody.length) return;
var rows = $tbody.find('tr').get();
rows.sort(function (a, b) {
var ta = $(a).data('title');
var tb = $(b).data('title');
var da = new Date($(a).data('time'));
var db = new Date($(b).data('time'));
if (mode === 'az') return ta.localeCompare(tb);
if (mode === 'za') return tb.localeCompare(ta);
if (mode === 'oldest') return da - db;
if (mode === 'newest') return db - da;
return 0;
});
$.each(rows, function (_, row) {
$tbody.append(row);
});
});
(function () {
// ===================================================================
// A) FLOATING CREATE BUTTON UI (unchanged, ES5)
// ===================================================================
function initCreateButton() {
if (typeof mw === 'undefined') return;
if (!mw.config.get('wgUserName')) return;
var groups = mw.config.get('wgUserGroups') || [];
var isEditor = groups.indexOf('editor') !== -1;
var isAdmin = groups.indexOf('sysop') !== -1;
var isWriter = groups.indexOf('writer') !== -1;
var isLead = groups.indexOf('lead') !== -1;
// Only Writers, Leads, Editors, Admins can use UI
if (!(isWriter || isLead || isEditor || isAdmin)) return;
// Avoid duplicate button
if (document.getElementById('floatingCreateBtn')) return;
// Floating button
var btn = document.createElement('button');
btn.id = 'floatingCreateBtn';
btn.appendChild(document.createTextNode('+'));
btn.style.cssText =
'position:fixed;bottom:30px;right:30px;width:60px;height:60px;' +
'border-radius:50%;background:#0078D7;color:#fff;font-size:32px;' +
'border:none;cursor:pointer;z-index:9999;box-shadow:0 4px 10px rgba(0,0,0,0.3);';
btn.onmouseover = function () { btn.style.background = '#005fa3'; };
btn.onmouseout = function () { btn.style.background = '#0078D7'; };
document.body.appendChild(btn);
// Popup box
var popup = document.createElement('div');
popup.id = 'createPopupBox';
popup.style.cssText =
'position:fixed;bottom:100px;right:30px;background:#fff;padding:15px;' +
'border:2px solid #ccc;border-radius:12px;width:300px;display:none;' +
'box-shadow:0 6px 14px rgba(0,0,0,0.25);z-index:10000;';
var html = '';
// Language selector
html += '<label style="font-weight:bold;">Select Language</label><br>';
html += '<select id="pageLang" style="width:100%;padding:6px;margin:6px 0;border:1px solid #ccc;border-radius:5px;">';
html += '<option value="en">English</option>';
html += '<option value="hi">Hindi</option>';
html += '<option value="ta">Tamil</option>';
html += '</select>';
// Page creation
html += '<label style="font-weight:bold;">Create new page</label>';
html += '<input type="text" id="newPageName" placeholder="Enter page name"' +
'style="width:100%;padding:6px;margin-top:4px;border:1px solid #ccc;border-radius:5px;">';
html += '<button id="createPageBtn" style="margin-top:8px;width:100%;background:#0078D7;' +
'color:white;border:none;padding:6px;border-radius:6px;cursor:pointer;">Create Page</button>';
// ---------- CATEGORY CREATION (Editor/Admin only) ----------
if (isEditor || isAdmin) {
html += '<hr style="margin:10px 0;">';
html += '<label style="font-weight:bold;">Create new category</label>';
html += '<input type="text" id="newCategoryName" placeholder="Enter category name"' +
'style="width:100%;padding:6px;margin-top:4px;border:1px solid #ccc;border-radius:5px;">';
html += '<button id="createCategoryBtn" style="margin-top:8px;width:100%;background:#28a745;' +
'color:white;border:none;padding:6px;border-radius:6px;cursor:pointer;">Create Category</button>';
}
// Dashboard + Mapping
html += '<hr style="margin:10px 0;">';
html += '<button id="openDashboardBtn" style="width:100%;background:#6f42c1;color:white;' +
'border:none;padding:6px;border-radius:6px;cursor:pointer;">Dashboard</button>';
html += '<button id="openMappingBtn" style="width:100%;background:#ff5733;color:white;' +
'border:none;padding:6px;border-radius:6px;margin-top:6px;cursor:pointer;">Mapping</button>';
popup.innerHTML = html;
document.body.appendChild(popup);
// Toggle popup
btn.onclick = function () {
popup.style.display = (popup.style.display === 'none') ? 'block' : 'none';
};
// Popup click handler
popup.onclick = function (ev) {
var target = ev.target;
// Create normal page
if (target.id === 'createPageBtn') {
var lang = document.getElementById('pageLang').value;
var name = document.getElementById('newPageName').value.trim();
if (!name) return alert("Please enter a page name");
var t = name.replace(/\s+/g, '_');
var url = (lang === 'hi') ? '/Hi/' + t :
(lang === 'ta') ? '/Ta/' + t :
'/' + t;
// No categories needed here; creating new page directly
window.location.href = url + '?action=edit&veaction=edit';
}
// Create category
if (target.id === 'createCategoryBtn') {
var cat = document.getElementById('newCategoryName').value.trim();
if (!cat) return alert("Please enter a category name");
var cTitle = 'Category:' + cat.replace(/\s+/g, '-');
window.location.href = '/' + cTitle + '?action=edit&veaction=edit';
}
if (target.id === 'openDashboardBtn')
window.location.href = '/Special:TeamDashboard';
if (target.id === 'openMappingBtn')
window.location.href = '/Special:TeamMapping';
};
}
if (document.readyState === 'complete' || document.readyState === 'interactive')
initCreateButton();
else
document.addEventListener('DOMContentLoaded', initCreateButton);
// ===================================================================
// B) CATEGORY + PARENT PAGE LOGIC (Original + Multilingual Enhancements)
// including category inheritance helpers
// ===================================================================
mw.loader.using(['mediawiki.util', 'mediawiki.api']).then(function () {
var api = new mw.Api();
var user = mw.config.get('wgUserName');
if (!user) return;
var ns = mw.config.get('wgNamespaceNumber');
var title = mw.config.get('wgTitle') || '';
var pagenm = mw.config.get('wgPageName') || '';
var action = mw.config.get('wgAction') || '';
var revId = mw.config.get('wgCurRevisionId') || 0;
var urlParams = new URLSearchParams(location.search);
var autoCat = urlParams.get('autocategory');
var skipParent = urlParams.get('skipparent');
// ---------------------------------------------------------------
// CATEGORY PROMPT (UNCHANGED)
// ---------------------------------------------------------------
if (ns === 14) { // Category namespace
var key = "category_prompt_done_" + title;
function askCategory() {
if (sessionStorage.getItem(key)) return;
sessionStorage.setItem(key, 'done');
var ask = confirm(
'You created category "' + title + '".\n\nCreate a page with this name?'
);
if (ask) {
window.location.href = mw.util.getUrl(title, {
action:'edit', veaction:'edit',
autocategory:title,
skipparent:1
});
}
}
mw.hook('postEdit').add(askCategory);
$(window).on('popstate', function () {
if (action !== 'view') return;
if (sessionStorage.getItem(key)) return;
api.get({
action:'query',
titles:'Category:' + title,
prop:'info'
}).done(function (res) {
var page = res.query.pages[Object.keys(res.query.pages)[0]];
if (page && !page.missing) askCategory();
});
});
}
// ---------------------------------------------------------------
// INJECT AUTOCATEGORY
// ---------------------------------------------------------------
function injectCategory(cat) {
var box = $('#wpTextbox1');
if (!box.length) return;
var tag = '[[Category:' + cat + ']]';
if (box.val().indexOf(tag) === -1)
box.val(tag + '\n\n' + box.val());
}
if (autoCat) {
$(function () { injectCategory(autoCat); });
mw.hook('ve.activationComplete').add(function () { injectCategory(autoCat); });
}
// ---------------------------------------------------------------
// LANGUAGE DETECTION
// ---------------------------------------------------------------
var isHindi = pagenm.indexOf("Hi/") === 0;
var isTamil = pagenm.indexOf("Ta/") === 0;
var langPrefix = isHindi ? "Hi/" : (isTamil ? "Ta/" : "");
// Remove prefix from title
function stripPrefix(t) {
return t.replace(/^Hi\//, "").replace(/^Ta\//, "");
}
// Build clean title
function buildTitle(parent, child) {
parent = stripPrefix(parent).replace(/^\/+|\/+$/g, "");
child = stripPrefix(child ).replace(/^\/+|\/+$/g, "");
if (!parent)
return langPrefix + child;
return langPrefix + parent + "/" + child;
}
// ---------------------------------------------------------------
// HELPER: Get parent categories (exposed globally)
// ---------------------------------------------------------------
window.getParentCategories = function (parentTitle, callback) {
if (!parentTitle) {
callback([]);
return;
}
api.get({
action: "query",
prop: "categories",
titles: parentTitle,
cllimit: "max"
}).done(function (res) {
try {
var page = res.query.pages[Object.keys(res.query.pages)[0]];
var cats = [];
if (page && page.categories) {
for (var i = 0; i < page.categories.length; i++) {
var c = page.categories[i].title.replace(/^Category:/, '');
cats.push(c);
}
}
callback(cats);
} catch (ee) {
callback([]);
}
}).fail(function () {
callback([]);
});
};
// ---------------------------------------------------------------
// HELPER: Insert categories into editor (VE or Source)
// Runs once per stored inheritCategories session value
// ---------------------------------------------------------------
function applyInheritedCategories() {
var data = sessionStorage.getItem("inheritCategories");
if (!data) return;
try {
var cats = JSON.parse(data);
} catch (e) {
sessionStorage.removeItem("inheritCategories");
return;
}
if (!cats || !cats.length) {
sessionStorage.removeItem("inheritCategories");
return;
}
// Remove to avoid double-insert
sessionStorage.removeItem("inheritCategories");
// Build wikitext fragment
var wikitext = "";
for (var i = 0; i < cats.length; i++) {
wikitext += "[[Category:" + cats[i] + "]]\n";
}
// 1) If source editor present, insert there
var box = document.getElementById("wpTextbox1");
if (box) {
box.value = wikitext + "\n" + box.value;
return;
}
// 2) VisualEditor - try to insert using VE model
try {
if (window.ve && ve.init && ve.init.target) {
var surface = ve.init.target.getSurface();
if (!surface || !surface.getModel) {
// VE not ready yet: retry shortly
setTimeout(applyInheritedCategories, 200);
return;
}
var model = surface.getModel();
// Attempt insertion; wrap in try/catch to avoid breaking if APIs differ
try {
model.getDocument().transact(model.insertContent(wikitext + "\n"));
} catch (e) {
// As a fallback, try to insert by collapsing selection and inserting text
try {
var frag = model.getDocument().getFragment();
if (frag && frag.collapseToEnd) {
frag.collapseToEnd();
frag.insertContent("\n" + wikitext);
}
} catch (e2) {
// give up silently - categories already removed from session
}
}
}
} catch (ee) {
// nothing
}
}
// Run at DOM ready (source editor case)
$(applyInheritedCategories);
// Run after VE activation (VE case)
mw.hook('ve.activationComplete').add(applyInheritedCategories);
// ---------------------------------------------------------------
// PARENT PAGE MODAL (made global so VE can call it: window.askParent)
// + Cancel and click-outside behavior
// ---------------------------------------------------------------
window.askParent = function (child, done) {
if (window.__parentModalShown) return;
window.__parentModalShown = true;
var o = $('<div>').css({
position:'fixed',top:0,left:0,width:'100%',height:'100%',
background:'rgba(0,0,0,0.5)',display:'flex',
justifyContent:'center',alignItems:'center',
zIndex:999999
});
var box = $('<div>').css({
background:'#fff',padding:'20px',width:'420px',
borderRadius:'10px',boxShadow:'0 4px 12px rgba(0,0,0,0.3)',
position:'relative'
});
var input = $('<input type="text" placeholder="Search parent...">').css({
width:'100%',padding:'8px',border:'1px solid #ccc',borderRadius:'6px'
});
var list = $('<ul>').css({
listStyle:'none',padding:0,margin:'10px 0 0',
maxHeight:'200px',overflowY:'auto',border:'1px solid #ddd',
borderRadius:'6px',display:'none'
});
var btns = $('<div>').css({ marginTop:'10px', textAlign:'right' });
var ok = $('<button>Confirm</button>').css({
background:'#007bff',color:'#fff',padding:'8px 14px',
border:'none',borderRadius:'6px',cursor:'pointer'
});
var skip = $('<button>No Parent</button>').css({
background:'#6c757d',color:'#fff',padding:'8px 14px',
border:'none',borderRadius:'6px',cursor:'pointer',
marginLeft:'8px'
});
var cancel = $('<button>Cancel</button>').css({
background:'#dc3545',color:'#fff',padding:'8px 14px',
border:'none',borderRadius:'6px',cursor:'pointer',
marginLeft:'8px'
});
btns.append(ok, skip, cancel);
box.append('<h3>Select Parent Page</h3>', input, list, btns);
o.append(box);
$('body').append(o);
var selected = '';
input.on('input', function () {
var q = $.trim(input.val());
list.empty();
if (!q) return list.hide();
api.get({
action:'opensearch',
search: langPrefix + q,
limit: 12,
namespace:0
}).done(function (res) {
var results = res[1] || [];
list.empty();
if (!results.length) return list.hide();
list.show();
for (var i = 0; i < results.length; i++) {
(function(pg){
var li = $('<li>').text(pg).css({
padding:'8px',cursor:'pointer'
}).hover(
function(){ $(this).css('background','#eee'); },
function(){ $(this).css('background',''); }
).click(function(){
selected = pg;
input.val(stripPrefix(pg));
list.hide();
});
list.append(li);
})(results[i]);
}
});
});
// Confirm
ok.on('click', function () {
var parent = selected || input.val().trim();
if (!parent) return alert("Select parent or click No Parent");
o.remove();
window.__parentModalShown = false;
done(parent);
});
// No Parent (explicitly remove parent)
skip.on('click', function () {
o.remove();
window.__parentModalShown = false;
done("");
});
// Cancel (do nothing)
cancel.on('click', function () {
o.remove();
window.__parentModalShown = false;
done(null);
});
// Click outside to close (acts like Cancel)
o.on('click', function (e) {
if (e.target === o[0]) {
o.remove();
window.__parentModalShown = false;
done(null);
}
});
};
// ---------------------------------------------------------------
// NEW PAGE HANDLING (when creating a new page via edit)
// — now stores parent categories into sessionStorage before redirect
// ---------------------------------------------------------------
function handleNewPage() {
if (ns !== 0) return;
if (action !== 'edit') return;
if (revId !== 0) return;
if (skipParent && autoCat) return;
askParent(pagenm, function (parent) {
// If user cancelled modal, parent === null
if (parent === null) return;
var finalTitle = buildTitle(parent, pagenm);
// If parent given, fetch its categories and store for the new page
if (parent) {
window.getParentCategories(parent, function(cats) {
try {
sessionStorage.setItem("inheritCategories", JSON.stringify(cats || []));
} catch (e) {}
// Redirect to edit (VE)
var url = mw.util.getUrl(finalTitle, { veaction:'edit' });
if (location.href !== url) location.href = url;
});
} else {
// Explicit "No Parent" — clear parent, but still open editor
try { sessionStorage.removeItem("inheritCategories"); } catch (e) {}
var url2 = mw.util.getUrl(finalTitle, { veaction:'edit' });
if (location.href !== url2) location.href = url2;
}
});
}
if (action === 'edit') handleNewPage();
mw.hook('ve.activationComplete').add(handleNewPage);
}); // end mw.loader.using for Section B
// ===================================================================
// C) CHANGE PARENT PAGE (VE PAGE SETTINGS ONLY) — CLEAN FINAL VERSION
// ===================================================================
mw.loader.using(['mediawiki.util', 'mediawiki.api']).then(function () {
if (window.__changeParentInjected) return;
window.__changeParentInjected = true;
var ns = mw.config.get('wgNamespaceNumber');
var action = mw.config.get('wgAction') || "";
var revId = mw.config.get('wgCurRevisionId') || 0;
// only main namespace & editing
if (ns !== 0) return;
var isVE = location.search.indexOf("veaction=edit") !== -1;
var isEdit = action === "edit";
if (!isVE && !isEdit) return;
if (!revId) return;
var groups = mw.config.get("wgUserGroups") || [];
var allowed =
groups.indexOf("writer") !== -1 ||
groups.indexOf("lead") !== -1 ||
groups.indexOf("editor") !== -1 ||
groups.indexOf("sysop") !== -1;
if (!allowed) return;
// Use a robust wait for VE toolbar + settings popup
function waitAndInject() {
var $settingsTool = $('span.oo-ui-tool-name-settings');
if (!$settingsTool.length) {
return setTimeout(waitAndInject, 200);
}
var $toolGroup = $settingsTool.closest(".oo-ui-toolGroup-tools");
if (!$toolGroup.length) {
return setTimeout(waitAndInject, 200);
}
if ($("#ve-change-parent-tool").length) return;
var $tool = $('<span>')
.attr("id", "ve-change-parent-tool")
.addClass("oo-ui-widget oo-ui-widget-enabled oo-ui-tool oo-ui-tool-link")
.css({ display: "block" });
var $link = $('<a>')
.addClass("oo-ui-tool-link")
.attr("tabindex", "0")
.css({
display: "flex",
alignItems: "center",
padding: "6px"
});
// Use hierarchy icon to avoid overlap problems
var $icon = $('<span>')
.addClass("oo-ui-iconElement-icon oo-ui-icon-hierarchy")
.css({ marginRight: "6px" });
var $label = $('<span>')
.addClass("oo-ui-tool-title")
.text("Change Parent");
$link.append($icon).append($label);
$tool.append($link);
$toolGroup.append($tool);
// OOUI-safe click
$link.on("click", function (e) {
e.preventDefault();
e.stopPropagation();
if (typeof window.askParent !== "function") {
alert("Parent selector not ready. Reload editor.");
return;
}
var fullTitle = mw.config.get("wgPageName") || "";
// Detect language prefix
var prefix = "";
if (fullTitle.indexOf("Hi/") === 0) prefix = "Hi/";
if (fullTitle.indexOf("Ta/") === 0) prefix = "Ta/";
var noPrefix = fullTitle.replace(/^Hi\//, "").replace(/^Ta\//, "");
var parts = noPrefix.split("/");
var child = parts[parts.length - 1];
window.askParent(fullTitle, function (parent) {
// parent === null means cancel
if (parent === null) return;
parent = parent.replace(/^Hi\//, "")
.replace(/^Ta\//, "")
.trim();
var newTitle = parent ?
prefix + parent + "/" + child :
prefix + child;
newTitle = newTitle.replace(/\/+/g, "/");
if (newTitle === fullTitle) {
alert("Parent not changed.");
return;
}
// Before moving, fetch parent categories for the new parent path
var parentForCats = parent || "";
if (parentForCats) {
// store categories so new page gets them after move+redirect
window.getParentCategories(parentForCats, function (cats) {
try {
sessionStorage.setItem("inheritCategories", JSON.stringify(cats || []));
} catch (e) {}
// perform move
movePage(fullTitle, newTitle);
});
} else {
// No parent selected (move to top-level)
try { sessionStorage.removeItem("inheritCategories"); } catch (e) {}
movePage(fullTitle, newTitle);
}
});
});
console.log("✔ Change Parent added to VE Page Settings");
}
// Wait for VE activation and then try injecting (more robust)
mw.hook('ve.activationComplete').add(function () {
waitAndInject();
});
// Also attempt immediately (in case VE already activated)
setTimeout(waitAndInject, 300);
// ===================================================================
// MOVE PAGE (unchanged except parent-cats handling is done before calling movePage)
// ===================================================================
function movePage(oldTitle, newTitle) {
var api = new mw.Api();
api.postWithToken("csrf", {
action: "move",
from: oldTitle,
to: newTitle,
reason: "Updated parent structure",
movetalk: 1,
noredirect: 1
})
.done(function () {
api.postWithToken("csrf", {
action: "delete",
title: oldTitle,
reason: "Old parent path cleaned after move"
})
.done(function () {
alert("Parent updated and old page deleted.");
// After move & delete we redirect to new title.
// applyInheritedCategories() will run on load because we pre-saved inheritCategories.
window.location.href = mw.util.getUrl(newTitle);
})
.fail(function (err) {
alert("Page moved, but deletion failed: " +
(err && err.error && err.error.info ? err.error.info : "Unknown"));
window.location.href = mw.util.getUrl(newTitle);
});
})
.fail(function (err) {
var msg = (err && err.error && err.error.info) ? err.error.info : "Unknown error";
alert("Move failed: " + msg);
});
}
}); // end Section C
})();
mw.loader.using(['jquery'], function () {
$(function () {
// Run only on Special:SpecialPages
if (mw.config.get('wgCanonicalSpecialPageName') !== 'SpecialPages') {
return;
}
var $heading = $('#mw-specialpagesgroup-other');
if (!$heading.length) {
console.warn('Other special pages heading not found');
return;
}
var $list = $heading.next('.mw-specialpages-list');
if (!$list.length) {
console.warn('Other special pages list not found');
return;
}
// Correct container for Chameleon & all modern skins
var $container = $('.mw-parser-output').first();
if (!$container.length) {
console.warn('Parser output container not found');
return;
}
// Move heading + list to top
$heading.add($list).prependTo($container);
});
});
