MediaWiki:Common.js: Difference between revisions
From Sanatan Hindu Dharma
No edit summary |
No edit summary |
||
| Line 572: | Line 572: | ||
( function ( mw, $ ) { | |||
'use strict'; | |||
mw.loader.using( [ 'mediawiki.util', 'mediawiki.api' ] ).then( function () { | |||
// Only logged-in users for category prompt behavior | |||
var isLoggedIn = !!mw.config.get( 'wgUserName' ); | |||
// Configuration | |||
var cfg = { | |||
' | minSearchChars: 2, | ||
); | searchLimit: 8, | ||
createLinkSelectors: 'a.new, a.mw-new, a.createpage, a[href*="redlink=1"]', | |||
subpageSeparator: '/', | |||
categoryNs: ( function () { | |||
// wgNamespaceIds may not be present in all installs; fallback to 14 | |||
var ids = mw.config.get( 'wgNamespaceIds' ); | |||
return ids && typeof ids.category !== 'undefined' ? ids.category : 14; | |||
} )() | |||
}; | |||
var api = new mw.Api(); | |||
/* ------------------------- Utility: modal / confirm ------------------------- */ | |||
} | // Prefer OOUI if available for nicer dialogs; fallback to built-in confirm/alert. | ||
function showConfirm( title, $content, okText, cancelText ) { | |||
okText = okText || ( mw.msg && mw.msg( 'ok' ) ) || 'OK'; | |||
cancelText = cancelText || ( mw.msg && mw.msg( 'cancel' ) ) || 'Cancel'; | |||
if ( window.OO && window.OO.ui && window.OO.ui.ProcessDialog && window.OO.ui.WindowManager ) { | |||
return new Promise( function ( resolve ) { | |||
// Create simple OOUI dialog | |||
var Dialog = function ( config ) { | |||
Dialog.super.call( this, config ); | |||
}; | |||
OO.inheritClass( Dialog, OO.ui.ProcessDialog ); | |||
Dialog.static.name = 'simpleDialog-' + Date.now(); | |||
Dialog.static.title = title; | |||
Dialog.prototype.initialize = function () { | |||
Dialog.super.prototype.initialize.call( this ); | |||
this.$content.append( $content ); | |||
this.actions = new OO.ui.ActionSet(); | |||
this.actions.add( new OO.ui.ActionLayout( new OO.ui.ButtonWidget( { label: cancelText } ), { align: 'start' } ) ); | |||
this.actions.add( new OO.ui.ActionLayout( new OO.ui.ButtonWidget( { label: okText, flags: [ 'primary' ] } ), { align: 'end' } ) ); | |||
this.$foot.append( this.actions.$element ); | |||
}; | |||
var manager = new OO.ui.WindowManager(); | |||
$( 'body' ).append( manager.$element ); | |||
var dialog = new Dialog(); | |||
manager.addWindows( [ dialog ] ); | |||
manager.openWindow( dialog ); | |||
dialog.actions.getButton( okText ).on( 'click', function () { | |||
manager.closeWindow( dialog ); | |||
resolve( true ); | |||
manager.destroy(); | |||
} ); | |||
dialog.actions.getButton( cancelText ).on( 'click', function () { | |||
manager.closeWindow( dialog ); | |||
resolve( false ); | |||
manager.destroy(); | |||
} ); | |||
} ); | |||
} else { | |||
// Fallback: use native confirm and show content text in prompt | |||
var txt = $content && $content.text ? $content.text() : ''; | |||
return Promise.resolve( window.confirm( title + '\n\n' + txt ) ); | |||
} | } | ||
} | } | ||
mw.hook('postEdit').add( | /* ------------------------- Category creation helper ------------------------- */ | ||
// Only for logged-in users: when a new Category is created, prompt to create main page | |||
( function categoryHelper() { | |||
if ( !isLoggedIn ) return; | |||
var ns = mw.config.get( 'wgNamespaceNumber' ); | |||
if ( ns !== cfg.categoryNs ) return; // only run on Category namespace pages | |||
var catBaseTitle = mw.config.get( 'wgTitle' ); // title without "Category:" prefix | |||
if ( !catBaseTitle ) return; | |||
// Use a storage key per category | |||
var storageKey = 'auto_create_page_for_category_' + catBaseTitle; | |||
// askAndCreate: prompt user and redirect to edit mainspace page with prefill if accepted | |||
function askAndCreate() { | |||
try { | |||
if ( sessionStorage.getItem( storageKey ) ) return; | |||
sessionStorage.setItem( storageKey, 'done' ); | |||
} catch ( e ) { | |||
// ignore storage failures | |||
} | |||
var $content = $( '<div/>' ).append( | |||
$( '<p/>' ).text( 'You just created the new category "' + catBaseTitle + '".' ), | |||
$( '<p/>' ).text( 'Do you want to create a normal page with the same name? It will automatically belong to this category.' ) | |||
); | |||
showConfirm( 'Create paired page?', $content, 'Yes, create', 'No' ).then( function ( ok ) { | |||
if ( !ok ) return; | |||
// Pre-fill the mainspace page (title = catBaseTitle) with the category link | |||
var mainTitle = catBaseTitle; | |||
var prefill = '[[' + 'Category:' + catBaseTitle + ']]\n\n'; | |||
try { | |||
sessionStorage.setItem( 'LeadWriterControl_prefill_' + mainTitle, prefill ); | |||
} catch ( e ) { | |||
// ignore | |||
} | |||
// Redirect to edit page for mainTitle | |||
window.location.href = mw.util.getUrl( mainTitle, { action: 'edit' } ); | |||
} ); | |||
} | |||
// Detect after save using mw.hook('postEdit') (works for SourceEditor), and also listen for VE save with popstate and ve.activationComplete. | |||
try { | |||
mw.hook( 'postEdit' ).add( function () { | |||
// Only fire if category page now exists (postEdit indicates a save happened) | |||
askAndCreate(); | |||
} ); | |||
} catch ( e ) { | |||
// ignore | |||
} | |||
// VE: sometimes history/pushState triggers navigation; detect landing on view after save | |||
$( window ).on( 'popstate', function () { | |||
// If we are on view mode and the category page exists and we haven't shown prompt yet — ask. | |||
if ( mw.config.get( 'wgAction' ) !== 'view' ) return; | |||
try { | |||
if ( sessionStorage.getItem( storageKey ) ) return; | |||
} catch ( e ) { /* ignore */ } | |||
// Verify the Category page exists (not missing) | |||
api.get( { | |||
action: 'query', | |||
titles: 'Category:' + catBaseTitle, | |||
prop: 'info', | |||
format: 'json' | |||
} ).done( function ( data ) { | |||
try { | |||
if ( data && data.query && data.query.pages ) { | |||
var page = Object.values( data.query.pages )[0]; | |||
if ( page && typeof page.missing === 'undefined' ) { | |||
askAndCreate(); | |||
} | |||
} | |||
} catch ( e ) { | |||
// ignore | |||
} | |||
} ).fail( function () { | |||
// ignore errors | |||
} ); | |||
} ); | |||
// If user arrived at edit page via our redirect with prefill param, apply it | |||
$( function () { | |||
var params = new URLSearchParams( window.location.search ); | |||
// If user navigated to edit main page and sessionStorage prefill exists, apply it to textarea when loaded | |||
if ( mw.config.get( 'wgAction' ) === 'edit' ) { | |||
var title = mw.config.get( 'wgPageName' ); | |||
if ( title ) { | |||
var key = 'LeadWriterControl_prefill_' + title; | |||
var pre = null; | |||
try { pre = sessionStorage.getItem( key ); } catch ( e ) { pre = null; } | |||
if ( pre ) { | |||
// Wait for textarea to be present | |||
setTimeout( function applyPrefill() { | |||
var $tb = $( '#wpTextbox1' ); | |||
if ( $tb.length && !$tb.val().trim() ) { | |||
$tb.val( pre ); | |||
try { sessionStorage.removeItem( key ); } catch ( e ) { /* ignore */ } | |||
} | |||
}, 500 ); | |||
} | |||
} | |||
} | |||
} ); | |||
} )(); | |||
/* ------------------------- Parent page prompt with live search ------------------------- */ | |||
// Search via action=opensearch is lightweight; use api.get for that. | |||
function searchPages( q ) { | |||
if ( !q || q.length < cfg.minSearchChars ) return Promise.resolve( [] ); | |||
}); | return api.get( { | ||
action: 'opensearch', | |||
search: q, | |||
limit: cfg.searchLimit, | |||
namespace: 0, | |||
format: 'json' | |||
} ).then( function ( data ) { | |||
// response: [searchTerm, [titles...], [descriptions...], [links...]] | |||
var titles = ( Array.isArray( data ) && Array.isArray( data[1] ) ) ? data[1] : []; | |||
return titles; | |||
} ).catch( function () { | |||
return []; | |||
} ); | |||
} | |||
box.append('<h3>Select Parent Page ( | // Create an accessible overlay with input + suggestions; resolves with chosen parent title string or null. | ||
function promptForParentTitle( initialValue ) { | |||
initialValue = initialValue || ''; | |||
return new Promise( function ( resolve ) { | |||
// Build elements | |||
var $overlay = $( '<div/>' ).css( { | |||
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, | |||
background: 'rgba(0,0,0,0.45)', display: 'flex', alignItems: 'center', justifyContent: 'center', | |||
zIndex: 999999 | |||
} ); | |||
var $box = $( '<div/>' ).css( { | |||
background: '#fff', padding: '18px', borderRadius: '8px', width: '420px', boxShadow: '0 6px 18px rgba(0,0,0,0.3)' | |||
} ); | |||
$box.append( $( '<h3/>' ).text( 'Select Parent Page' ) ); | |||
var $input = $( '<input type="search" aria-label="Search pages" />' ).css( { | |||
width: '100%', padding: '8px', border: '1px solid #ccc', borderRadius: '4px' | |||
} ).val( initialValue ); | |||
var $list = $( '<ul/>' ).css( { | |||
listStyle: 'none', margin: '10px 0 0', padding: 0, maxHeight: '150px', overflowY: 'auto', border: '1px solid #eee', borderRadius: '4px' | |||
} ).hide(); | |||
var $btns = $( '<div/>' ).css( { marginTop: '12px', textAlign: 'right' } ); | |||
var $confirm = $( '<button/>' ).text( 'Confirm' ).css( { marginRight: '8px' } ); | |||
var $noParent = $( '<button/>' ).text( 'No Parent' ); | |||
$btns.append( $confirm, $noParent ); | |||
$box.append( $input, $list, $btns ); | |||
$overlay.append( $box ); | |||
$( 'body' ).append( $overlay ); | |||
var debounce = null; | |||
$input.on( 'input', function () { | |||
var q = $input.val().trim(); | |||
if ( debounce ) clearTimeout( debounce ); | |||
debounce = setTimeout( function () { | |||
if ( q.length < cfg.minSearchChars ) { | |||
$list.empty().hide(); | |||
return; | |||
} | |||
$list.empty().show().append( $( '<li/>' ).text( 'Searching…' ).css( { color: '#666', padding: '6px' } ) ); | |||
searchPages( q ).then( function ( titles ) { | |||
$list.empty(); | |||
if ( !titles || titles.length === 0 ) { | |||
$list.append( $( '<li/>' ).text( 'No results' ).css( { color: '#666', padding: '6px' } ) ); | |||
return; | |||
} | |||
titles.forEach( function ( t ) { | |||
var $li = $( '<li/>' ).text( t ).css( { padding: '6px 10px', cursor: 'pointer' } ); | |||
$li.on( 'click', function () { | |||
$input.val( t ); | |||
$list.hide(); | |||
} ); | |||
$li.hover( function () { $( this ).css( 'background', '#f5f5f5' ); }, function () { $( this ).css( 'background', '' ); } ); | |||
$list.append( $li ); | |||
} ); | |||
} ); | |||
}, 220 ); | |||
} ); | |||
$confirm.on( 'click', function () { | |||
var val = $input.val().trim(); | |||
cleanup(); | |||
if ( val ) resolve( val ); | |||
else resolve( null ); | |||
} ); | |||
$noParent.on( 'click', function () { | |||
// Confirm the user intention | |||
cleanup(); | |||
if ( window.confirm && window.confirm( 'Proceed without a parent page?' ) ) { | |||
resolve( null ); | |||
} else { | |||
resolve( false ); // signal "cancelled" vs explicit null? We'll treat false as cancel. | |||
} | |||
} ); | |||
// prevent clicking overlay to close accidentally | |||
$overlay.on( 'click', function ( e ) { | |||
if ( e.target === $overlay[0] ) { | |||
// ignore | |||
} | |||
} ); | |||
// helper to remove overlay | |||
function cleanup() { | |||
$overlay.remove(); | |||
} | |||
// prefill suggestions if initialValue provided | |||
if ( initialValue && initialValue.length >= cfg.minSearchChars ) { | |||
$input.trigger( 'input' ); | |||
} | |||
} ); | |||
} | |||
// Ask for parent when creating a new page in main namespace | |||
function askForParentIfNewPage() { | |||
var action = mw.config.get( 'wgAction' ); | |||
if ( action !== 'edit' ) return; | |||
var ns = mw.config.get( 'wgNamespaceNumber' ); | |||
if ( ns !== 0 ) return; // only main namespace | |||
// | // Detect new page: wgCurRevisionId is 0 for new pages in many installs; also check wgIsArticle/other heuristics | ||
var curRev = mw.config.get( 'wgCurRevisionId' ); | |||
var isNew = ( curRev === 0 || typeof curRev === 'undefined' && mw.config.get( 'wgIsArticle' ) === false ); | |||
// Some installs expose wgArticleId; if 0 => new | |||
var artId = mw.config.get( 'wgArticleId' ); | |||
if ( typeof artId !== 'undefined' && artId === 0 ) isNew = true; | |||
if ( !isNew ) return; | |||
if ( | |||
}); | // Show prompt (avoid repeated prompts) | ||
if ( window._leadwriter_parent_shown ) return; | |||
window._leadwriter_parent_shown = true; | |||
var title = mw.config.get( 'wgPageName' ); // prefixed name (no namespace for main) | |||
setTimeout( function () { | |||
promptForParentTitle( '' ).then( function ( selected ) { | |||
// If user clicked Cancel (we used false) just return without navigating | |||
if ( selected === false ) { | |||
window._leadwriter_parent_shown = false; | |||
return; | |||
} | |||
// selected === null => explicit "No Parent" — open current edit | |||
if ( selected === null ) { | |||
window.location.href = mw.util.getUrl( title, { veaction: 'edit' } ); | |||
return; | |||
} | |||
// Otherwise build parent/child title | |||
var childSegment = title.indexOf( cfg.subpageSeparator ) === -1 ? title : title.split( cfg.subpageSeparator ).pop(); | |||
var newTitle = selected + cfg.subpageSeparator + childSegment; | |||
if ( window.confirm && !window.confirm( 'Create under: ' + newTitle + ' ?' ) ) { | |||
// cancelled by user | |||
window._leadwriter_parent_shown = false; | |||
return; | |||
} | |||
// navigate to VisualEditor edit for newTitle (veaction) | |||
window.location.href = mw.util.getUrl( newTitle, { veaction: 'edit' } ); | |||
} ); | |||
}, 800 ); | |||
} | } | ||
function | // Initialize: run for source edit pages and for VE activation | ||
if ( | $( function () { | ||
// On normal edit page load | |||
if ( mw.config.get( 'wgAction' ) === 'edit' ) { | |||
askForParentIfNewPage(); | |||
} | |||
// VE: wait until VisualEditor is activated | |||
try { | |||
mw.hook( 've.activationComplete' ).add( function () { | |||
// VE is ready; VE may be used to edit a new page so ask | |||
askForParentIfNewPage(); | |||
} ); | |||
} catch ( e ) { | |||
// ignore | |||
} | |||
} ); | |||
/* ------------------------- Attach delegated handler to any create links (optional) ------------------------- */ | |||
// Intercept redlink-style create links and prompt parent selection prior to navigation. | |||
// This is optional and won't run on all link types, but helps UX for "redlinks" in page view. | |||
$( document ).on( 'click', cfg.createLinkSelectors, function ( e ) { | |||
// Only relevant when left-click and no modifier | |||
if ( e.isDefaultPrevented() ) return; | |||
if ( e.which && e.which !== 1 ) return; | |||
// Determine if this is an internal link to create a page by reading title param from href | |||
var href = $( this ).attr( 'href' ) || ''; | |||
// Skip external links | |||
if ( href.indexOf( 'http://' ) === 0 || href.indexOf( 'https://' ) === 0 || href.indexOf( '//' ) === 0 ) return; | |||
var titleParam = null; | |||
try { | |||
// Parse possible title parameter if link is like index.php?title=Foo&redlink=1 | |||
var parts = href.split( '?' ); | |||
if ( parts.length > 1 ) { | |||
var qs = parts[1]; | |||
var pairs = qs.split( '&' ); | |||
pairs.forEach( function ( p ) { | |||
var kv = p.split( '=' ); | |||
if ( kv[0] === 'title' ) titleParam = decodeURIComponent( kv[1] || '' ); | |||
} ); | |||
} | |||
// If no title param, fall back to data-title or link text | |||
if ( !titleParam ) { | |||
titleParam = $( this ).data( 'title' ) || $( this ).attr( 'title' ) || $( this ).text().trim(); | |||
} | |||
} catch ( e ) { | |||
titleParam = $( this ).text().trim(); | |||
} | } | ||
if ( !titleParam ) return; | |||
// Only act on main namespace titles (heuristic): skip if contains ":" indicating other namespace | |||
if ( titleParam.indexOf( ':' ) !== -1 ) return; | |||
e.preventDefault(); | |||
promptForParentTitle( '' ).then( function ( parent ) { | |||
if ( parent === false ) { | |||
// canceled | |||
return; | |||
} | |||
if ( parent === null ) { | |||
// No parent chosen; open original link | |||
window.location.href = mw.util.getUrl( titleParam, { veaction: 'edit' } ); | |||
return; | |||
} | |||
var childSegment = titleParam.indexOf( cfg.subpageSeparator ) === -1 ? titleParam : titleParam.split( cfg.subpageSeparator ).pop(); | |||
var newTitle = parent + cfg.subpageSeparator + childSegment; | |||
window.location.href = mw.util.getUrl( newTitle, { veaction: 'edit' } ); | |||
} ); | |||
} ); | |||
/* ------------------------- Reminder banner on edit page ------------------------- */ | |||
// Add a small tip banner on the edit page reminding to set a parent | |||
try { | |||
mw.hook( 'wikipage.content' ).add( function ( $content ) { | |||
var action = mw.config.get( 'wgAction' ); | |||
if ( action === 'edit' || action === 'submit' ) { | |||
var $banner = $( '<div/>' ).css( { | |||
background: '#fff8c4', border: '1px solid #e0c14b', padding: '10px', marginBottom: '10px', | |||
borderRadius: '6px', textAlign: 'center', fontWeight: 600 | |||
} ).text( '🪶 Tip: Remember to set a parent page before saving.' ); | |||
$content.prepend( $banner ); | |||
} | |||
} ); | |||
} catch ( e ) { | |||
// ignore if hook not available | |||
} | } | ||
/* ------------------------------------------------------------------------- */ | |||
// Safe init log (optional) | |||
if ( window.console ) console.log( 'LeadWriterControl merged script initialized.' ); | |||
} ).catch( function ( err ) { | |||
if ( window.console ) console.error( 'LeadWriterControl loader error:', err ); | |||
} ); | |||
} )( mediaWiki, jQuery ); | |||
Revision as of 15:47, 5 November 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');
}
});
*/
/* 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 ---
mw.loader.using(['mediawiki.user'], function () {
$(function () {
mw.user.getGroups().then(function (groups) {
// If the user is NOT an admin (sysop)
if (groups.indexOf('sysop') === -1) {
// Disable Tools menu and its dropdown items
$('a, span').filter(function () {
return $(this).text().trim() === 'Tools';
}).each(function () {
const link = $(this);
link.css({
'pointer-events': 'none',
'opacity': '0.5',
'cursor': 'not-allowed'
});
link.attr('title', 'Restricted to admins');
link.closest('.dropdown').find('a').css({
'pointer-events': 'none',
'opacity': '0.5',
'cursor': 'not-allowed'
});
});
// Disable any link to Special:SpecialPages
$('a[href*="Special:SpecialPages"]').css({
'pointer-events': 'none',
'opacity': '0.5',
'cursor': 'not-allowed'
}).attr('title', 'Restricted to admins');
// Disable MediaWiki namespace links
$('a[href*="MediaWiki:"]').css({
'pointer-events': 'none',
'opacity': '0.5',
'cursor': 'not-allowed'
}).attr('title', 'Restricted to admins');
}
});
});
});
// Collapsible Related Pages on Mobile
mw.loader.using('jquery', function () {
$(function () {
var header = $('.page-links-header');
var content = $('.page-links-content');
if (header.length && content.length) {
header.on('click', function () {
$(this).toggleClass('active');
content.toggleClass('open');
});
}
});
});
/* === Auto-Link Page Titles in Paragraphs (exclude header/footer) === */
(function (mw, $) {
'use strict';
var MAX_TITLES = 1000;
function escapeRegExp(s) {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function run(titles) {
if (!titles || titles.length === 0) return;
var filtered = titles
.filter(function (t) {
return t && t.length > 3;
})
.sort(function (a, b) {
return b.length - a.length;
})
.slice(0, MAX_TITLES);
// Select only main content paragraphs (not header/footer)
$('#content p').each(function () {
if ($(this).find('a, code, pre, .no-auto-link').length) return;
var html = $(this).html();
filtered.forEach(function (title) {
var display = title.includes(':')
? title.split(':').pop()
: title;
var pattern = display.replace(/[_-]+/g, '[ _-]');
var re = new RegExp('\\b' + escapeRegExp(pattern) + '\\b', 'gi');
html = html.replace(re, function (match) {
return (
'<a href="' +
mw.util.getUrl(title) +
'" class="auto-linked-page">' +
match +
'</a>'
);
});
});
$(this).html(html);
});
}
$(function () {
var title = mw.config.get('wgPageName');
if (!title) return;
$.getJSON(
mw.util.wikiScript() +
'?title=Special:AutoLinkTitles&format=json&pageTitle=' +
encodeURIComponent(title)
)
.done(function (data) {
if (data && Array.isArray(data.titles)) {
run(data.titles);
}
})
.fail(function () {
console.warn('AutoLinkTitles: failed to load title list.');
});
});
})(mediaWiki, jQuery);
// == Showing Template as default footer
$(document).ready(function () {
// Remove Chameleon’s default footer
$('.footercontainer, #footer, #mw-footer').remove();
// Prevent duplicate inserts
if ($('#mw-custom-footer').length) return;
// Load your custom footer
$.ajax({
url: '/index.php?title=Template:Custom-footer&action=render',
type: 'GET',
success: function (data) {
const footer = $('<div id="mw-custom-footer"></div>').html(data);
$('body').append(footer);
},
error: function () {
console.warn('⚠️ Custom footer could not be loaded.');
}
});
});
( function ( mw, $ ) {
'use strict';
mw.loader.using( [ 'mediawiki.util', 'mediawiki.api' ] ).then( function () {
// Only logged-in users for category prompt behavior
var isLoggedIn = !!mw.config.get( 'wgUserName' );
// Configuration
var cfg = {
minSearchChars: 2,
searchLimit: 8,
createLinkSelectors: 'a.new, a.mw-new, a.createpage, a[href*="redlink=1"]',
subpageSeparator: '/',
categoryNs: ( function () {
// wgNamespaceIds may not be present in all installs; fallback to 14
var ids = mw.config.get( 'wgNamespaceIds' );
return ids && typeof ids.category !== 'undefined' ? ids.category : 14;
} )()
};
var api = new mw.Api();
/* ------------------------- Utility: modal / confirm ------------------------- */
// Prefer OOUI if available for nicer dialogs; fallback to built-in confirm/alert.
function showConfirm( title, $content, okText, cancelText ) {
okText = okText || ( mw.msg && mw.msg( 'ok' ) ) || 'OK';
cancelText = cancelText || ( mw.msg && mw.msg( 'cancel' ) ) || 'Cancel';
if ( window.OO && window.OO.ui && window.OO.ui.ProcessDialog && window.OO.ui.WindowManager ) {
return new Promise( function ( resolve ) {
// Create simple OOUI dialog
var Dialog = function ( config ) {
Dialog.super.call( this, config );
};
OO.inheritClass( Dialog, OO.ui.ProcessDialog );
Dialog.static.name = 'simpleDialog-' + Date.now();
Dialog.static.title = title;
Dialog.prototype.initialize = function () {
Dialog.super.prototype.initialize.call( this );
this.$content.append( $content );
this.actions = new OO.ui.ActionSet();
this.actions.add( new OO.ui.ActionLayout( new OO.ui.ButtonWidget( { label: cancelText } ), { align: 'start' } ) );
this.actions.add( new OO.ui.ActionLayout( new OO.ui.ButtonWidget( { label: okText, flags: [ 'primary' ] } ), { align: 'end' } ) );
this.$foot.append( this.actions.$element );
};
var manager = new OO.ui.WindowManager();
$( 'body' ).append( manager.$element );
var dialog = new Dialog();
manager.addWindows( [ dialog ] );
manager.openWindow( dialog );
dialog.actions.getButton( okText ).on( 'click', function () {
manager.closeWindow( dialog );
resolve( true );
manager.destroy();
} );
dialog.actions.getButton( cancelText ).on( 'click', function () {
manager.closeWindow( dialog );
resolve( false );
manager.destroy();
} );
} );
} else {
// Fallback: use native confirm and show content text in prompt
var txt = $content && $content.text ? $content.text() : '';
return Promise.resolve( window.confirm( title + '\n\n' + txt ) );
}
}
/* ------------------------- Category creation helper ------------------------- */
// Only for logged-in users: when a new Category is created, prompt to create main page
( function categoryHelper() {
if ( !isLoggedIn ) return;
var ns = mw.config.get( 'wgNamespaceNumber' );
if ( ns !== cfg.categoryNs ) return; // only run on Category namespace pages
var catBaseTitle = mw.config.get( 'wgTitle' ); // title without "Category:" prefix
if ( !catBaseTitle ) return;
// Use a storage key per category
var storageKey = 'auto_create_page_for_category_' + catBaseTitle;
// askAndCreate: prompt user and redirect to edit mainspace page with prefill if accepted
function askAndCreate() {
try {
if ( sessionStorage.getItem( storageKey ) ) return;
sessionStorage.setItem( storageKey, 'done' );
} catch ( e ) {
// ignore storage failures
}
var $content = $( '<div/>' ).append(
$( '<p/>' ).text( 'You just created the new category "' + catBaseTitle + '".' ),
$( '<p/>' ).text( 'Do you want to create a normal page with the same name? It will automatically belong to this category.' )
);
showConfirm( 'Create paired page?', $content, 'Yes, create', 'No' ).then( function ( ok ) {
if ( !ok ) return;
// Pre-fill the mainspace page (title = catBaseTitle) with the category link
var mainTitle = catBaseTitle;
var prefill = '[[' + 'Category:' + catBaseTitle + ']]\n\n';
try {
sessionStorage.setItem( 'LeadWriterControl_prefill_' + mainTitle, prefill );
} catch ( e ) {
// ignore
}
// Redirect to edit page for mainTitle
window.location.href = mw.util.getUrl( mainTitle, { action: 'edit' } );
} );
}
// Detect after save using mw.hook('postEdit') (works for SourceEditor), and also listen for VE save with popstate and ve.activationComplete.
try {
mw.hook( 'postEdit' ).add( function () {
// Only fire if category page now exists (postEdit indicates a save happened)
askAndCreate();
} );
} catch ( e ) {
// ignore
}
// VE: sometimes history/pushState triggers navigation; detect landing on view after save
$( window ).on( 'popstate', function () {
// If we are on view mode and the category page exists and we haven't shown prompt yet — ask.
if ( mw.config.get( 'wgAction' ) !== 'view' ) return;
try {
if ( sessionStorage.getItem( storageKey ) ) return;
} catch ( e ) { /* ignore */ }
// Verify the Category page exists (not missing)
api.get( {
action: 'query',
titles: 'Category:' + catBaseTitle,
prop: 'info',
format: 'json'
} ).done( function ( data ) {
try {
if ( data && data.query && data.query.pages ) {
var page = Object.values( data.query.pages )[0];
if ( page && typeof page.missing === 'undefined' ) {
askAndCreate();
}
}
} catch ( e ) {
// ignore
}
} ).fail( function () {
// ignore errors
} );
} );
// If user arrived at edit page via our redirect with prefill param, apply it
$( function () {
var params = new URLSearchParams( window.location.search );
// If user navigated to edit main page and sessionStorage prefill exists, apply it to textarea when loaded
if ( mw.config.get( 'wgAction' ) === 'edit' ) {
var title = mw.config.get( 'wgPageName' );
if ( title ) {
var key = 'LeadWriterControl_prefill_' + title;
var pre = null;
try { pre = sessionStorage.getItem( key ); } catch ( e ) { pre = null; }
if ( pre ) {
// Wait for textarea to be present
setTimeout( function applyPrefill() {
var $tb = $( '#wpTextbox1' );
if ( $tb.length && !$tb.val().trim() ) {
$tb.val( pre );
try { sessionStorage.removeItem( key ); } catch ( e ) { /* ignore */ }
}
}, 500 );
}
}
}
} );
} )();
/* ------------------------- Parent page prompt with live search ------------------------- */
// Search via action=opensearch is lightweight; use api.get for that.
function searchPages( q ) {
if ( !q || q.length < cfg.minSearchChars ) return Promise.resolve( [] );
return api.get( {
action: 'opensearch',
search: q,
limit: cfg.searchLimit,
namespace: 0,
format: 'json'
} ).then( function ( data ) {
// response: [searchTerm, [titles...], [descriptions...], [links...]]
var titles = ( Array.isArray( data ) && Array.isArray( data[1] ) ) ? data[1] : [];
return titles;
} ).catch( function () {
return [];
} );
}
// Create an accessible overlay with input + suggestions; resolves with chosen parent title string or null.
function promptForParentTitle( initialValue ) {
initialValue = initialValue || '';
return new Promise( function ( resolve ) {
// Build elements
var $overlay = $( '<div/>' ).css( {
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
background: 'rgba(0,0,0,0.45)', display: 'flex', alignItems: 'center', justifyContent: 'center',
zIndex: 999999
} );
var $box = $( '<div/>' ).css( {
background: '#fff', padding: '18px', borderRadius: '8px', width: '420px', boxShadow: '0 6px 18px rgba(0,0,0,0.3)'
} );
$box.append( $( '<h3/>' ).text( 'Select Parent Page' ) );
var $input = $( '<input type="search" aria-label="Search pages" />' ).css( {
width: '100%', padding: '8px', border: '1px solid #ccc', borderRadius: '4px'
} ).val( initialValue );
var $list = $( '<ul/>' ).css( {
listStyle: 'none', margin: '10px 0 0', padding: 0, maxHeight: '150px', overflowY: 'auto', border: '1px solid #eee', borderRadius: '4px'
} ).hide();
var $btns = $( '<div/>' ).css( { marginTop: '12px', textAlign: 'right' } );
var $confirm = $( '<button/>' ).text( 'Confirm' ).css( { marginRight: '8px' } );
var $noParent = $( '<button/>' ).text( 'No Parent' );
$btns.append( $confirm, $noParent );
$box.append( $input, $list, $btns );
$overlay.append( $box );
$( 'body' ).append( $overlay );
var debounce = null;
$input.on( 'input', function () {
var q = $input.val().trim();
if ( debounce ) clearTimeout( debounce );
debounce = setTimeout( function () {
if ( q.length < cfg.minSearchChars ) {
$list.empty().hide();
return;
}
$list.empty().show().append( $( '<li/>' ).text( 'Searching…' ).css( { color: '#666', padding: '6px' } ) );
searchPages( q ).then( function ( titles ) {
$list.empty();
if ( !titles || titles.length === 0 ) {
$list.append( $( '<li/>' ).text( 'No results' ).css( { color: '#666', padding: '6px' } ) );
return;
}
titles.forEach( function ( t ) {
var $li = $( '<li/>' ).text( t ).css( { padding: '6px 10px', cursor: 'pointer' } );
$li.on( 'click', function () {
$input.val( t );
$list.hide();
} );
$li.hover( function () { $( this ).css( 'background', '#f5f5f5' ); }, function () { $( this ).css( 'background', '' ); } );
$list.append( $li );
} );
} );
}, 220 );
} );
$confirm.on( 'click', function () {
var val = $input.val().trim();
cleanup();
if ( val ) resolve( val );
else resolve( null );
} );
$noParent.on( 'click', function () {
// Confirm the user intention
cleanup();
if ( window.confirm && window.confirm( 'Proceed without a parent page?' ) ) {
resolve( null );
} else {
resolve( false ); // signal "cancelled" vs explicit null? We'll treat false as cancel.
}
} );
// prevent clicking overlay to close accidentally
$overlay.on( 'click', function ( e ) {
if ( e.target === $overlay[0] ) {
// ignore
}
} );
// helper to remove overlay
function cleanup() {
$overlay.remove();
}
// prefill suggestions if initialValue provided
if ( initialValue && initialValue.length >= cfg.minSearchChars ) {
$input.trigger( 'input' );
}
} );
}
// Ask for parent when creating a new page in main namespace
function askForParentIfNewPage() {
var action = mw.config.get( 'wgAction' );
if ( action !== 'edit' ) return;
var ns = mw.config.get( 'wgNamespaceNumber' );
if ( ns !== 0 ) return; // only main namespace
// Detect new page: wgCurRevisionId is 0 for new pages in many installs; also check wgIsArticle/other heuristics
var curRev = mw.config.get( 'wgCurRevisionId' );
var isNew = ( curRev === 0 || typeof curRev === 'undefined' && mw.config.get( 'wgIsArticle' ) === false );
// Some installs expose wgArticleId; if 0 => new
var artId = mw.config.get( 'wgArticleId' );
if ( typeof artId !== 'undefined' && artId === 0 ) isNew = true;
if ( !isNew ) return;
// Show prompt (avoid repeated prompts)
if ( window._leadwriter_parent_shown ) return;
window._leadwriter_parent_shown = true;
var title = mw.config.get( 'wgPageName' ); // prefixed name (no namespace for main)
setTimeout( function () {
promptForParentTitle( '' ).then( function ( selected ) {
// If user clicked Cancel (we used false) just return without navigating
if ( selected === false ) {
window._leadwriter_parent_shown = false;
return;
}
// selected === null => explicit "No Parent" — open current edit
if ( selected === null ) {
window.location.href = mw.util.getUrl( title, { veaction: 'edit' } );
return;
}
// Otherwise build parent/child title
var childSegment = title.indexOf( cfg.subpageSeparator ) === -1 ? title : title.split( cfg.subpageSeparator ).pop();
var newTitle = selected + cfg.subpageSeparator + childSegment;
if ( window.confirm && !window.confirm( 'Create under: ' + newTitle + ' ?' ) ) {
// cancelled by user
window._leadwriter_parent_shown = false;
return;
}
// navigate to VisualEditor edit for newTitle (veaction)
window.location.href = mw.util.getUrl( newTitle, { veaction: 'edit' } );
} );
}, 800 );
}
// Initialize: run for source edit pages and for VE activation
$( function () {
// On normal edit page load
if ( mw.config.get( 'wgAction' ) === 'edit' ) {
askForParentIfNewPage();
}
// VE: wait until VisualEditor is activated
try {
mw.hook( 've.activationComplete' ).add( function () {
// VE is ready; VE may be used to edit a new page so ask
askForParentIfNewPage();
} );
} catch ( e ) {
// ignore
}
} );
/* ------------------------- Attach delegated handler to any create links (optional) ------------------------- */
// Intercept redlink-style create links and prompt parent selection prior to navigation.
// This is optional and won't run on all link types, but helps UX for "redlinks" in page view.
$( document ).on( 'click', cfg.createLinkSelectors, function ( e ) {
// Only relevant when left-click and no modifier
if ( e.isDefaultPrevented() ) return;
if ( e.which && e.which !== 1 ) return;
// Determine if this is an internal link to create a page by reading title param from href
var href = $( this ).attr( 'href' ) || '';
// Skip external links
if ( href.indexOf( 'http://' ) === 0 || href.indexOf( 'https://' ) === 0 || href.indexOf( '//' ) === 0 ) return;
var titleParam = null;
try {
// Parse possible title parameter if link is like index.php?title=Foo&redlink=1
var parts = href.split( '?' );
if ( parts.length > 1 ) {
var qs = parts[1];
var pairs = qs.split( '&' );
pairs.forEach( function ( p ) {
var kv = p.split( '=' );
if ( kv[0] === 'title' ) titleParam = decodeURIComponent( kv[1] || '' );
} );
}
// If no title param, fall back to data-title or link text
if ( !titleParam ) {
titleParam = $( this ).data( 'title' ) || $( this ).attr( 'title' ) || $( this ).text().trim();
}
} catch ( e ) {
titleParam = $( this ).text().trim();
}
if ( !titleParam ) return;
// Only act on main namespace titles (heuristic): skip if contains ":" indicating other namespace
if ( titleParam.indexOf( ':' ) !== -1 ) return;
e.preventDefault();
promptForParentTitle( '' ).then( function ( parent ) {
if ( parent === false ) {
// canceled
return;
}
if ( parent === null ) {
// No parent chosen; open original link
window.location.href = mw.util.getUrl( titleParam, { veaction: 'edit' } );
return;
}
var childSegment = titleParam.indexOf( cfg.subpageSeparator ) === -1 ? titleParam : titleParam.split( cfg.subpageSeparator ).pop();
var newTitle = parent + cfg.subpageSeparator + childSegment;
window.location.href = mw.util.getUrl( newTitle, { veaction: 'edit' } );
} );
} );
/* ------------------------- Reminder banner on edit page ------------------------- */
// Add a small tip banner on the edit page reminding to set a parent
try {
mw.hook( 'wikipage.content' ).add( function ( $content ) {
var action = mw.config.get( 'wgAction' );
if ( action === 'edit' || action === 'submit' ) {
var $banner = $( '<div/>' ).css( {
background: '#fff8c4', border: '1px solid #e0c14b', padding: '10px', marginBottom: '10px',
borderRadius: '6px', textAlign: 'center', fontWeight: 600
} ).text( '🪶 Tip: Remember to set a parent page before saving.' );
$content.prepend( $banner );
}
} );
} catch ( e ) {
// ignore if hook not available
}
/* ------------------------------------------------------------------------- */
// Safe init log (optional)
if ( window.console ) console.log( 'LeadWriterControl merged script initialized.' );
} ).catch( function ( err ) {
if ( window.console ) console.error( 'LeadWriterControl loader error:', err );
} );
} )( mediaWiki, jQuery );
