MediaWiki:Common.js: Difference between revisions

From Sanatan Hindu Dharma
No edit summary
No edit summary
Line 565: Line 565:


});
});




Line 577: Line 578:
     mw.loader.using( [ 'mediawiki.util', 'mediawiki.api' ] ).then( function () {
     mw.loader.using( [ 'mediawiki.util', 'mediawiki.api' ] ).then( function () {


         // Only logged-in users for category prompt behavior
         const api = new mw.Api();
         var isLoggedIn = !!mw.config.get( 'wgUserName' );
         const isLoggedIn = !!mw.config.get( 'wgUserName' );
 
         const cfg = {
        // Configuration
         var cfg = {
             minSearchChars: 2,
             minSearchChars: 2,
             searchLimit: 8,
             searchLimit: 8,
            createLinkSelectors: 'a.new, a.mw-new, a.createpage, a[href*="redlink=1"]',
             subpageSeparator: '/',
             subpageSeparator: '/',
             categoryNs: ( function () {
             categoryNs: 14 // Category namespace
                // 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();
         /* ------------------------- Category Prompt ------------------------- */
        if ( isLoggedIn && mw.config.get('wgNamespaceNumber') === cfg.categoryNs ) {
            const catBaseTitle = mw.config.get('wgTitle');
            const storageKey = 'auto_create_page_for_category_' + catBaseTitle;


        /* ------------------------- Utility: modal / confirm ------------------------- */
            function askAndCreateCategoryPage() {
                if ( sessionStorage.getItem( storageKey ) ) return;
                sessionStorage.setItem( storageKey, 'done' );


        // Prefer OOUI if available for nicer dialogs; fallback to built-in confirm/alert.
                const ok = window.confirm(
        function showConfirm( title, $content, okText, cancelText ) {
                     `✅ You just created the new category "${catBaseTitle}".\n\n` +
            okText = okText || ( mw.msg && mw.msg( 'ok' ) ) || 'OK';
                    `Do you want to create a mainspace page with the same name? It will automatically belong to this category.`
            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
                if ( ok ) {
            function askAndCreate() {
                     const mainTitle = catBaseTitle; // main namespace page
                try {
                     const url = mw.util.getUrl(mainTitle, {
                     if ( sessionStorage.getItem( storageKey ) ) return;
                        action: 'edit',
                     sessionStorage.setItem( storageKey, 'done' );
                        veaction: 'edit',
                } catch ( e ) {
                        preloadtext: '[[' + 'Category:' + catBaseTitle + ']]\n\n'
                     // ignore storage failures
                    });
                     window.location.href = url;
                 }
                 }
                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.
             // Trigger after save (SourceEditor + VE)
             try {
             mw.hook('postEdit').add(askAndCreateCategoryPage);
                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
             // Fallback for VE popstate
             $( window ).on( 'popstate', function () {
             $(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;
                 if ( mw.config.get( 'wgAction' ) !== 'view' ) return;
                 if ( sessionStorage.getItem(storageKey) ) return;
                 try {
                    if ( sessionStorage.getItem( storageKey ) ) return;
                } catch ( e ) { /* ignore */ }


                // Verify the Category page exists (not missing)
                 api.get({
                 api.get( {
                     action: 'query',
                     action: 'query',
                     titles: 'Category:' + catBaseTitle,
                     titles: 'Category:' + catBaseTitle,
                     prop: 'info',
                     prop: 'info',
                     format: 'json'
                     format: 'json'
                 } ).done( function ( data ) {
                 }).done(function (data) {
                     try {
                     const page = Object.values(data.query.pages)[0];
                        if ( data && data.query && data.query.pages ) {
                    if ( page && typeof page.missing === 'undefined' ) {
                            var page = Object.values( data.query.pages )[0];
                        askAndCreateCategoryPage();
                            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
         /* ------------------------- Parent Page Prompt ------------------------- */
            $( function () {
         function searchPages( query ) {
                var params = new URLSearchParams( window.location.search );
             if ( !query || query.length < cfg.minSearchChars ) return Promise.resolve([]);
                // If user navigated to edit main page and sessionStorage prefill exists, apply it to textarea when loaded
             return api.get({
                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',
                 action: 'opensearch',
                 search: q,
                 search: query,
                 limit: cfg.searchLimit,
                 limit: cfg.searchLimit,
                 namespace: 0,
                 namespace: 0
                format: 'json'
             }).then(data => data[1] || []).catch(() => []);
             } ).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() {
         function promptForParentTitle( initialValue ) {
             return new Promise( resolve => {
            initialValue = initialValue || '';
                 const $overlay = $('<div>').css({
             return new Promise( function ( resolve ) {
                     position: 'fixed', top:0,left:0,width:'100%',height:'100%',
                 // Build elements
                     background:'rgba(0,0,0,0.45)', display:'flex', alignItems:'center', justifyContent:'center',
                var $overlay = $( '<div/>' ).css( {
                     zIndex:999999
                     position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
                 });
                     background: 'rgba(0,0,0,0.45)', display: 'flex', alignItems: 'center', justifyContent: 'center',
                 const $box = $('<div>').css({
                     zIndex: 999999
                     background:'#fff', padding:'18px', borderRadius:'8px', width:'420px', boxShadow:'0 6px 18px rgba(0,0,0,0.3)'
                 } );
                 });
                 var $box = $( '<div/>' ).css( {
                 $box.append($('<h3/>').text('Select Parent Page'));
                     background: '#fff', padding: '18px', borderRadius: '8px', width: '420px', boxShadow: '0 6px 18px rgba(0,0,0,0.3)'
                 const $input = $('<input type="search"/>').css({ width:'100%', padding:'8px', border:'1px solid #ccc', borderRadius:'4px' });
                 } );
                 const $list = $('<ul/>').css({ listStyle:'none', margin:'10px 0 0', padding:0, maxHeight:'150px', overflowY:'auto', border:'1px solid #eee', borderRadius:'4px' }).hide();
                 $box.append( $( '<h3/>' ).text( 'Select Parent Page' ) );
                 const $btns = $('<div/>').css({ marginTop:'12px', textAlign:'right' });
                 var $input = $( '<input type="search" aria-label="Search pages" />' ).css( {
                 const $confirm = $('<button/>').text('Confirm').css({ marginRight:'8px' });
                    width: '100%', padding: '8px', border: '1px solid #ccc', borderRadius: '4px'
                 const $noParent = $('<button/>').text('No Parent');
                } ).val( initialValue );
                 $btns.append($confirm,$noParent);
                 var $list = $( '<ul/>' ).css( {
                 $box.append($input,$list,$btns);
                    listStyle: 'none', margin: '10px 0 0', padding: 0, maxHeight: '150px', overflowY: 'auto', border: '1px solid #eee', borderRadius: '4px'
                 $overlay.append($box);
                } ).hide();
                 $('body').append($overlay);
                 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;
                 let debounce;
                 $input.on( 'input', function () {
                 $input.on('input', function(){
                     var q = $input.val().trim();
                     const q = $input.val().trim();
                     if ( debounce ) clearTimeout( debounce );
                     if ( debounce ) clearTimeout(debounce);
                     debounce = setTimeout( function () {
                     debounce = setTimeout(()=>{
                         if ( q.length < cfg.minSearchChars ) {
                         if ( q.length < cfg.minSearchChars ) { $list.empty().hide(); return; }
                            $list.empty().hide();
                         $list.empty().show().append($('<li/>').text('Searching…').css({ color:'#666', padding:'6px' }));
                            return;
                         searchPages(q).then(titles=>{
                        }
                         $list.empty().show().append( $( '<li/>' ).text( 'Searching…' ).css( { color: '#666', padding: '6px' } ) );
                         searchPages( q ).then( function ( titles ) {
                             $list.empty();
                             $list.empty();
                             if ( !titles || titles.length === 0 ) {
                             if (!titles.length){ $list.append($('<li/>').text('No results').css({color:'#666',padding:'6px'})); return; }
                                $list.append( $( '<li/>' ).text( 'No results' ).css( { color: '#666', padding: '6px' } ) );
                             titles.forEach(t=>{
                                return;
                                 const $li = $('<li/>').text(t).css({ padding:'6px 10px', cursor:'pointer' });
                            }
                                 $li.on('click', ()=>{ $input.val(t); $list.hide(); });
                             titles.forEach( function ( t ) {
                                 $li.hover(()=>{$li.css('background','#f5f5f5');}, ()=>{$li.css('background','');});
                                 var $li = $( '<li/>' ).text( t ).css( { padding: '6px 10px', cursor: 'pointer' } );
                                 $list.append($li);
                                 $li.on( 'click', function () {
                             });
                                    $input.val( t );
                         });
                                    $list.hide();
                     },220);
                                } );
                 });
                                 $li.hover( function () { $( this ).css( 'background', '#f5f5f5' ); }, function () { $( this ).css( 'background', '' ); } );
                                 $list.append( $li );
                             } );
                         } );
                     }, 220 );
                 } );


                 $confirm.on( 'click', function () {
                 $confirm.on('click', ()=>{ cleanup(); resolve($input.val().trim() || null); });
                    var val = $input.val().trim();
                $noParent.on('click', ()=>{ cleanup(); resolve(null); });
                    cleanup();
                 $overlay.on('click', e=>{ if(e.target===$overlay[0]){} });
                    if ( val ) resolve( val );
                    else resolve( null );
                 } );


                 $noParent.on( 'click', function () {
                 function cleanup(){ $overlay.remove(); }
                    // 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 askParentIfNewPage() {
         function askForParentIfNewPage() {
             if ( mw.config.get('wgAction') !== 'edit' ) return;
             var action = mw.config.get( 'wgAction' );
             if ( mw.config.get('wgNamespaceNumber') !== 0 ) return;
            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)
             const pageKey = 'parent_prompt_' + mw.config.get('wgPageName');
             if ( window._leadwriter_parent_shown ) return;
             if ( sessionStorage.getItem(pageKey) ) return;
             window._leadwriter_parent_shown = true;
             sessionStorage.setItem(pageKey,'done');


            var title = mw.config.get( 'wgPageName' ); // prefixed name (no namespace for main)
             setTimeout(async ()=>{
             setTimeout( function () {
                 const selected = await promptForParentTitle();
                 promptForParentTitle( '' ).then( function ( selected ) {
                const title = mw.config.get('wgPageName');
                    // If user clicked Cancel (we used false) just return without navigating
                if ( selected === null ) { // No parent
                    if ( selected === false ) {
                     window.location.href = mw.util.getUrl(title, { veaction:'edit' });
                        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;
                     return;
                 }
                 }
                 if ( parent === null ) {
                 const childSegment = title.includes(cfg.subpageSeparator) ? title.split(cfg.subpageSeparator).pop() : title;
                    // No parent chosen; open original link
                 const newTitle = selected + cfg.subpageSeparator + childSegment;
                    window.location.href = mw.util.getUrl( titleParam, { veaction: 'edit' } );
                 window.location.href = mw.util.getUrl(newTitle, { veaction:'edit' });
                    return;
             }, 600);
                }
         }
                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 ------------------------- */
         $(function(){
            if ( mw.config.get('wgAction')==='edit' ) askParentIfNewPage();
            try{ mw.hook('ve.activationComplete').add(askParentIfNewPage); }catch(e){}
        });


         // Add a small tip banner on the edit page reminding to set a parent
         /* ------------------------- Reminder banner ------------------------- */
         try {
         try{
             mw.hook( 'wikipage.content' ).add( function ( $content ) {
             mw.hook('wikipage.content').add(function($content){
                 var action = mw.config.get( 'wgAction' );
                 const action = mw.config.get('wgAction');
                 if ( action === 'edit' || action === 'submit' ) {
                 if ( action==='edit' || action==='submit' ){
                     var $banner = $( '<div/>' ).css( {
                     $content.prepend($('<div/>').css({
                         background: '#fff8c4', border: '1px solid #e0c14b', padding: '10px', marginBottom: '10px',
                         background:'#fff8c4',border:'1px solid #e0c14b',padding:'10px',marginBottom:'10px',borderRadius:'6px',textAlign:'center',fontWeight:600
                        borderRadius: '6px', textAlign: 'center', fontWeight: 600
                     }).text('🪶 Tip: Remember to set a parent page before saving.'));
                     } ).text( '🪶 Tip: Remember to set a parent page before saving.' );
                    $content.prepend( $banner );
                 }
                 }
             } );
             });
         } catch ( e ) {
         }catch(e){}
            // ignore if hook not available
        }
 
        /* ------------------------------------------------------------------------- */


         // Safe init log (optional)
         console.log('✅ Category + Parent prompt JS initialized.');
        if ( window.console ) console.log( 'LeadWriterControl merged script initialized.' );


     } ).catch( function ( err ) {
     }).catch( err => console.error('LeadWriterControl loader error:', err) );
        if ( window.console ) console.error( 'LeadWriterControl loader error:', err );
    } );


} )( mediaWiki, jQuery );
} )( mediaWiki, jQuery );

Revision as of 15:48, 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 () {

        const api = new mw.Api();
        const isLoggedIn = !!mw.config.get( 'wgUserName' );
        const cfg = {
            minSearchChars: 2,
            searchLimit: 8,
            subpageSeparator: '/',
            categoryNs: 14 // Category namespace
        };

        /* ------------------------- Category Prompt ------------------------- */
        if ( isLoggedIn && mw.config.get('wgNamespaceNumber') === cfg.categoryNs ) {
            const catBaseTitle = mw.config.get('wgTitle');
            const storageKey = 'auto_create_page_for_category_' + catBaseTitle;

            function askAndCreateCategoryPage() {
                if ( sessionStorage.getItem( storageKey ) ) return;
                sessionStorage.setItem( storageKey, 'done' );

                const ok = window.confirm(
                    `✅ You just created the new category "${catBaseTitle}".\n\n` +
                    `Do you want to create a mainspace page with the same name? It will automatically belong to this category.`
                );

                if ( ok ) {
                    const mainTitle = catBaseTitle; // main namespace page
                    const url = mw.util.getUrl(mainTitle, {
                        action: 'edit',
                        veaction: 'edit',
                        preloadtext: '[[' + 'Category:' + catBaseTitle + ']]\n\n'
                    });
                    window.location.href = url;
                }
            }

            // Trigger after save (SourceEditor + VE)
            mw.hook('postEdit').add(askAndCreateCategoryPage);

            // Fallback for VE popstate
            $(window).on('popstate', function () {
                if ( mw.config.get('wgAction') !== 'view' ) return;
                if ( sessionStorage.getItem(storageKey) ) return;

                api.get({
                    action: 'query',
                    titles: 'Category:' + catBaseTitle,
                    prop: 'info',
                    format: 'json'
                }).done(function (data) {
                    const page = Object.values(data.query.pages)[0];
                    if ( page && typeof page.missing === 'undefined' ) {
                        askAndCreateCategoryPage();
                    }
                });
            });
        }

        /* ------------------------- Parent Page Prompt ------------------------- */
        function searchPages( query ) {
            if ( !query || query.length < cfg.minSearchChars ) return Promise.resolve([]);
            return api.get({
                action: 'opensearch',
                search: query,
                limit: cfg.searchLimit,
                namespace: 0
            }).then(data => data[1] || []).catch(() => []);
        }

        function promptForParentTitle() {
            return new Promise( resolve => {
                const $overlay = $('<div>').css({
                    position: 'fixed', top:0,left:0,width:'100%',height:'100%',
                    background:'rgba(0,0,0,0.45)', display:'flex', alignItems:'center', justifyContent:'center',
                    zIndex:999999
                });
                const $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'));
                const $input = $('<input type="search"/>').css({ width:'100%', padding:'8px', border:'1px solid #ccc', borderRadius:'4px' });
                const $list = $('<ul/>').css({ listStyle:'none', margin:'10px 0 0', padding:0, maxHeight:'150px', overflowY:'auto', border:'1px solid #eee', borderRadius:'4px' }).hide();
                const $btns = $('<div/>').css({ marginTop:'12px', textAlign:'right' });
                const $confirm = $('<button/>').text('Confirm').css({ marginRight:'8px' });
                const $noParent = $('<button/>').text('No Parent');
                $btns.append($confirm,$noParent);
                $box.append($input,$list,$btns);
                $overlay.append($box);
                $('body').append($overlay);

                let debounce;
                $input.on('input', function(){
                    const q = $input.val().trim();
                    if ( debounce ) clearTimeout(debounce);
                    debounce = setTimeout(()=>{
                        if ( q.length < cfg.minSearchChars ) { $list.empty().hide(); return; }
                        $list.empty().show().append($('<li/>').text('Searching…').css({ color:'#666', padding:'6px' }));
                        searchPages(q).then(titles=>{
                            $list.empty();
                            if (!titles.length){ $list.append($('<li/>').text('No results').css({color:'#666',padding:'6px'})); return; }
                            titles.forEach(t=>{
                                const $li = $('<li/>').text(t).css({ padding:'6px 10px', cursor:'pointer' });
                                $li.on('click', ()=>{ $input.val(t); $list.hide(); });
                                $li.hover(()=>{$li.css('background','#f5f5f5');}, ()=>{$li.css('background','');});
                                $list.append($li);
                            });
                        });
                    },220);
                });

                $confirm.on('click', ()=>{ cleanup(); resolve($input.val().trim() || null); });
                $noParent.on('click', ()=>{ cleanup(); resolve(null); });
                $overlay.on('click', e=>{ if(e.target===$overlay[0]){} });

                function cleanup(){ $overlay.remove(); }
            });
        }

        function askParentIfNewPage() {
            if ( mw.config.get('wgAction') !== 'edit' ) return;
            if ( mw.config.get('wgNamespaceNumber') !== 0 ) return;

            const pageKey = 'parent_prompt_' + mw.config.get('wgPageName');
            if ( sessionStorage.getItem(pageKey) ) return;
            sessionStorage.setItem(pageKey,'done');

            setTimeout(async ()=>{
                const selected = await promptForParentTitle();
                const title = mw.config.get('wgPageName');
                if ( selected === null ) { // No parent
                    window.location.href = mw.util.getUrl(title, { veaction:'edit' });
                    return;
                }
                const childSegment = title.includes(cfg.subpageSeparator) ? title.split(cfg.subpageSeparator).pop() : title;
                const newTitle = selected + cfg.subpageSeparator + childSegment;
                window.location.href = mw.util.getUrl(newTitle, { veaction:'edit' });
            }, 600);
        }

        $(function(){
            if ( mw.config.get('wgAction')==='edit' ) askParentIfNewPage();
            try{ mw.hook('ve.activationComplete').add(askParentIfNewPage); }catch(e){}
        });

        /* ------------------------- Reminder banner ------------------------- */
        try{
            mw.hook('wikipage.content').add(function($content){
                const action = mw.config.get('wgAction');
                if ( action==='edit' || action==='submit' ){
                    $content.prepend($('<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.'));
                }
            });
        }catch(e){}

        console.log('✅ Category + Parent prompt JS initialized.');

    }).catch( err => console.error('LeadWriterControl loader error:', err) );

} )( mediaWiki, jQuery );