Narodowe Centrum NaukiProjekt sfinansowany przez Narodowe Centrum Nauki
OPUS 2025/57/B/HS3/02198
Instytut Historii PAN

MediaWiki:Common.js: Różnice pomiędzy wersjami

Z LawRus
auto-sync z repo
auto-sync z repo
 
(Nie pokazano 2 pośrednich wersji utworzonych przez tego samego użytkownika)
Linia 80: Linia 80:
     setTimeout( window.lawrusTruncateAll, 300 );
     setTimeout( window.lawrusTruncateAll, 300 );
} );
} );
function lawrusInitPdfButton() {
    var $entry = $( '.wpis-kroniki' );
    if ( !$entry.length ) return;
    var $actions = $entry.find( '.lawrus-actions' );
    if ( !$actions.length ) return;
    var $btn = $( '<button class="lawrus-pdf-btn" type="button">↓ Pobierz PDF</button>' );
    $btn.on( 'click', function () {
        window.print();
    } );
    $actions.append( $btn );
}
/* -- Lightbox dla zalacznikow graficznych -----------------------------------
  Klik w miniaturke zalacznika (obraz) pokazuje pelny obraz w nakladce NA
  stronie wpisu, zamiast nawigowac do pliku. Zamkniecie: X / klik w tlo / Esc
  — uzytkownik wraca dokladnie na wpis. Bez JS link dziala normalnie (href to
  bezposredni URL pliku, ustawiony przez link={{filepath:}} w szablonie). */
function lawrusInitAttachmentLightbox() {
    $( document ).on( 'click', '.lawrus-attachment-img a', function ( e ) {
        var href = $( this ).attr( 'href' );
        if ( !href ) return;
        e.preventDefault();
        var alt = $( this ).find( 'img' ).attr( 'alt' ) || '';
        var $overlay = $(
            '<div class="lawrus-lightbox-overlay" role="dialog" aria-modal="true" aria-label="Podgląd zdjęcia / Image preview">' +
              '<button type="button" class="lawrus-lightbox-close" aria-label="Zamknij / Close">×</button>' +
              '<img class="lawrus-lightbox-img" alt="">' +
            '</div>'
        );
        $overlay.find( '.lawrus-lightbox-img' ).attr( { src: href, alt: alt } );
        function close() {
            $overlay.remove();
            $( document ).off( 'keydown.lawrusLightbox' );
        }
        // Klik w tlo lub w przycisk X zamyka; klik w sam obraz — nie.
        $overlay.on( 'click', function ( ev ) {
            if ( ev.target === this || $( ev.target ).hasClass( 'lawrus-lightbox-close' ) ) {
                close();
            }
        } );
        $( document ).on( 'keydown.lawrusLightbox', function ( ev ) {
            if ( ev.key === 'Escape' || ev.keyCode === 27 ) {
                close();
            }
        } );
        $( document.body ).append( $overlay );
    } );
}
/* -- Okienko wyboru jezyka (pierwsza wizyta) --------------------------------
  Cookie lawrus_lang = pl|en. Czytane TEZ po stronie serwera (LocalSettings.php,
  hook BeforeInitialize) zeby przy kolejnych wejsciach od razu serwowac strone
  w wybranym jezyku interfejsu (uselang). Surowa nazwa cookie, bez prefiksu MW. */
function lawrusGetCookie( name ) {
    var m = document.cookie.match( '(?:^|; )' + name + '=([^;]*)' );
    return m ? decodeURIComponent( m[ 1 ] ) : null;
}
function lawrusSetLangCookie( lang, persist ) {
    var c = 'lawrus_lang=' + lang + '; path=/; SameSite=Lax';
    if ( persist ) { c += '; max-age=' + ( 60 * 60 * 24 * 365 ); } // 1 rok
    // bez max-age -> cookie sesyjne: zapyta przy nastepnym otwarciu przegladarki
    if ( window.location.protocol === 'https:' ) { c += '; Secure'; }
    document.cookie = c;
}
/* Jawny ?uselang=en|pl w URL = swiadomy wybor jezyka (przycisk LangSwitch na
  stronach statycznych/Katalog, przelacznik PL/EN we wpisie i w drilldownie).
  Zapisujemy go w cookie lawrus_lang, zeby serwer (BeforeInitialize) utrzymal ten
  jezyk takze przy kolejnej nawigacji -- inaczej menu MediaWiki (Nawigacja, Strona
  glowna...) wracaloby do PL po kliknieciu w pasek boczny (linki sidebara nie nosza
  uselang). Cookie roczne -- wybor jest swiadomy. Dziala dla anonimow (zalogowani
  maja jezyk w preferencjach konta). Po zapisie tlumi tez okienko pierwszej wizyty. */
function lawrusSyncLangCookie() {
    var ul = new URL( window.location.href ).searchParams.get( 'uselang' );
    if ( ul !== 'en' && ul !== 'pl' ) { return; }
    if ( lawrusGetCookie( 'lawrus_lang' ) === ul ) { return; }
    lawrusSetLangCookie( ul, true );
}
function lawrusInitLangPrompt() {
    // wgUserId jest null dla anonimow, liczba dla zalogowanych (ci maja
    // jezyk w preferencjach konta, nie pokazujemy im okienka).
    if ( mw.config.get( 'wgUserId' ) ) { return; }
    if ( lawrusGetCookie( 'lawrus_lang' ) ) { return; } // wybor juz zapisany
    var $overlay = $(
        '<div class="lawrus-lang-modal-overlay" role="dialog" aria-modal="true" aria-label="Choose language / Wybierz jezyk">' +
          '<div class="lawrus-lang-modal">' +
            '<p class="lawrus-lang-modal-title">Choose your language<br>Wybierz język</p>' +
            '<div class="lawrus-lang-modal-buttons">' +
              '<button type="button" data-lang="en">Continue in English</button>' +
              '<button type="button" data-lang="pl">Kontynuj po polsku</button>' +
            '</div>' +
            '<label class="lawrus-lang-modal-remember">' +
              '<input type="checkbox" id="lawrus-lang-remember"> ' +
              'Don’t show this message again / Nie pokazuj więcej tego komunikatu' +
            '</label>' +
          '</div>' +
        '</div>'
    );
    $overlay.on( 'click', 'button[data-lang]', function () {
        var lang    = $( this ).data( 'lang' );
        var persist = $overlay.find( '#lawrus-lang-remember' ).prop( 'checked' );
        lawrusSetLangCookie( lang, persist );
        // Zapisalismy preferencje w cookie -> przeladowujemy bez jawnego uselang,
        // zeby zadecydowal serwer (LocalSettings.php): dla EN ustawi interfejs
        // angielski I przekieruje polska strone statyczna na bliźniaka EN
        // (Strona główna -> Main Page itd.) -- dokladnie jak przycisk PL/EN.
        var url = new URL( window.location.href );
        url.searchParams.delete( 'uselang' );
        if ( url.toString() === window.location.href ) {
            // URL bez zmian (np. PL na stronie PL) -> serwer i tak nic nie zmieni,
            // wystarczy zamknac okienko. Dla EN URL tez sie nie zmieni, ale
            // przeladowanie jest potrzebne by serwer zadzialal -> wymuszamy nizej.
            if ( lang === 'en' ) {
                window.location.reload();
            } else {
                $overlay.remove();
            }
        } else {
            window.location.href = url.toString();
        }
    } );
    $( document.body ).append( $overlay );
}


function lawrusInitLangSwitch() {
function lawrusInitLangSwitch() {
Linia 106: Linia 241:
             var lang = $( this ).data( 'lang' );
             var lang = $( this ).data( 'lang' );
             var url = new URL( window.location.href );
             var url = new URL( window.location.href );
             if ( lang === 'en' ) {
             // Jawny uselang (takze 'pl') zostawiamy w URL, zeby wygral z cookie
                url.searchParams.set( 'uselang', 'en' );
            // preferencji ustawionym przez okienko wyboru jezyka.
            } else {
            url.searchParams.set( 'uselang', lang === 'en' ? 'en' : 'pl' );
                url.searchParams.delete( 'uselang' );
            }
             window.location.href = url.toString();
             window.location.href = url.toString();
         } );
         } );
Linia 132: Linia 265:
         var lang = $( this ).data( 'lang' );
         var lang = $( this ).data( 'lang' );
         var url = new URL( window.location.href );
         var url = new URL( window.location.href );
         if ( lang === 'en' ) {
         // Jawny uselang (takze 'pl') wygrywa z cookie preferencji jezyka.
            url.searchParams.set( 'uselang', 'en' );
        url.searchParams.set( 'uselang', lang === 'en' ? 'en' : 'pl' );
        } else {
            url.searchParams.delete( 'uselang' );
        }
         window.location.href = url.toString();
         window.location.href = url.toString();
    } );
}
/* -- Dodawanie wpisu: duplikat nazwy + propozycja nowej nazwy ----------------
  Na Specjalna:FormStart przed wysłaniem sprawdza, czy wpis o tej nazwie już
  istnieje. Jeśli tak — pyta: otworzyć istniejący czy utworzyć nowy. Dla nowego
  proponuje nazwę (wiodąca liczba +1, a gdy brak liczby — prefiks "0_") i wstawia
  ją do pola JAKO PROPOZYCJĘ — użytkownik może ją zmienić; nic nie dzieje się
  automatycznie. */
function lawrusEscapeRegex( s ) {
    return s.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
}
/* Propozycja nazwy nowego wpisu na podstawie nazwy wpisanej i listy wszystkich
  tytułów (NS_MAIN). Reguły:
  - nazwa bez wiodącej liczby (np. "test") lub z prefiksem licznika i spacją
    (stare "0 test"): baza = nazwa bez prefiksu; szukamy najwyższego istniejącego
    "N-baza"/"N baza" i proponujemy (max+1)-baza (gdy brak — "0-baza"). Dzięki
    temu przy istniejących "test" i "0-test" propozycją jest "1-test".
    Separator dodajemy jako MYŚLNIK "-" — MediaWiki NIE zamienia go na spację
    (w przeciwieństwie do "_"), więc pozostaje widoczny w tytule.
  - nazwa "rocznikowa"/z wiodącą liczbą (np. "1022-Rus-Prawda", "0-test"):
    wiodąca liczba +1 (liczba znacząca = rok lub kolejny numer). */
function lawrusProposeEntryName( name, titles ) {
    // MediaWiki traktuje "_" i spację w tytułach równoważnie i zwraca formę ze
    // spacjami (allpages: "0_test" -> "0 test"). Normalizujemy "_" do spacji.
    var norm = function ( s ) { return String( s ).replace( /_/g, ' ' ).trim(); };
    var nName = norm( name );
    var base;
    var pm = nName.match( /^\d+\s+([\s\S]+)$/ ); // stara forma "N baza" (separator: spacja/_)
    if ( pm ) {
        base = pm[ 1 ];
    } else if ( /^\d/.test( nName ) ) {
        // wiodąca liczba (rocznik "1022-Rus-Prawda" lub licznik "0-test") -> +1
        var ym = nName.match( /^(\d+)([\s\S]*)$/ );
        return ( parseInt( ym[ 1 ], 10 ) + 1 ) + ym[ 2 ];
    } else {
        base = nName;
    }
    // Najwyższe istniejące "N-baza" / "N baza" (separator: myślnik lub spacja/_).
    var re = new RegExp( '^(\\d+)[\\s-]+' + lawrusEscapeRegex( base ) + '$', 'i' );
    var max = -1;
    ( titles || [] ).forEach( function ( t ) {
        var m = norm( t ).match( re );
        if ( m ) { max = Math.max( max, parseInt( m[ 1 ], 10 ) ); }
    } );
    return ( max + 1 ) + '-' + base;
}
function lawrusInitFormStartGuard() {
    if ( mw.config.get( 'wgCanonicalSpecialPageName' ) !== 'FormStart' ) { return; }
    var $form = $( '.pfFormInputWrapper' ).closest( 'form' );
    if ( !$form.length ) { return; }
    function askDuplicate( name, $input ) {
        var safe = mw.html.escape( name );
        var $overlay = $(
            '<div class="lawrus-lang-modal-overlay" role="dialog" aria-modal="true">' +
              '<div class="lawrus-lang-modal">' +
                '<p class="lawrus-lang-modal-title">' +
                  'Wpis „' + safe + '” już istnieje.<br>' +
                  'An entry named “' + safe + '” already exists.' +
                '</p>' +
                '<div class="lawrus-lang-modal-buttons">' +
                  '<button type="button" data-act="existing">Otwórz istniejący / Open existing</button>' +
                  '<button type="button" data-act="new">Utwórz nowy / Create new</button>' +
                '</div>' +
                '<label class="lawrus-lang-modal-remember">' +
                  '<a href="#" data-act="cancel">Anuluj / Cancel</a>' +
                '</label>' +
              '</div>' +
            '</div>'
        );
        $overlay.on( 'click', '[data-act]', function ( e ) {
            e.preventDefault();
            var act = $( this ).data( 'act' );
            $overlay.remove();
            if ( act === 'existing' ) {
                $form[ 0 ].submit(); // natywny submit pomija ten handler -> edycja istniejącego
            } else if ( act === 'new' ) {
                // tylko propozycja — pobierz wszystkie tytuły, policz rodzeństwo
                // "N_baza" i wstaw propozycję do pola (użytkownik może ją zmienić).
                var applyProposal = function ( titles ) {
                    $input.val( lawrusProposeEntryName( name, titles ) )
                        .trigger( 'input' ).trigger( 'change' ).trigger( 'focus' );
                };
                new mw.Api().get( {
                    action: 'query', list: 'allpages', apnamespace: 0,
                    aplimit: 'max', formatversion: 2
                } ).done( function ( d ) {
                    var titles = ( ( d.query && d.query.allpages ) || [] )
                        .map( function ( p ) { return p.title; } );
                    applyProposal( titles );
                } ).fail( function () {
                    applyProposal( [] );
                } );
            }
        } );
        $( document.body ).append( $overlay );
    }
    $form.on( 'submit', function ( e ) {
        var $input = $form.find( 'input[name="page_name"]' );
        var name = $.trim( $input.val() || '' );
        if ( name === '' ) { return; } // puste — niech obsłuży PHP
        e.preventDefault();
        mw.loader.using( 'mediawiki.api' ).done( function () {
            new mw.Api().get( {
                action: 'query', titles: name, formatversion: 2
            } ).done( function ( data ) {
                var pages = ( data.query && data.query.pages ) || [];
                var exists = pages.length && !pages[ 0 ].missing && !pages[ 0 ].invalid;
                if ( exists ) {
                    askDuplicate( name, $input );
                } else {
                    $form[ 0 ].submit(); // wolna nazwa — twórz nowy
                }
            } ).fail( function () {
                $form[ 0 ].submit(); // błąd API — nie blokuj użytkownika
            } );
        } );
     } );
     } );
}
}
Linia 176: Linia 431:
     console.log( '[LawRus] Common.js: document.ready, ns=' + mw.config.get( 'wgNamespaceNumber' ) );
     console.log( '[LawRus] Common.js: document.ready, ns=' + mw.config.get( 'wgNamespaceNumber' ) );


    lawrusSyncLangCookie();
    lawrusInitLangPrompt();
    lawrusInitFormStartGuard();
    lawrusInitPdfButton();
    lawrusInitAttachmentLightbox();
     lawrusInitLangSwitch();
     lawrusInitLangSwitch();
     lawrusInitDrilldownLangSwitch();
     lawrusInitDrilldownLangSwitch();

Aktualna wersja na dzień 09:12, 22 cze 2026

/* --- LawRus -- Common.js ---------------------------------------------------
   1. Przenoszenie sekcji uczestnikow pod wlasciwe sekcje Umow w formularzu.
   2. Walidacja formularza WpisKroniki przed zapisem (status >= 4).
   3. Skracanie streszczenia w tabelach Cargo do 100 znakow + rozwin.
   -------------------------------------------------------------------------- */

/* DataTables: wylacz autoWidth zanim Cargo zainicjuje tabele.
   preInit.dt odpala przed kazdym new DataTable() -- niezaleznie od kolejnosci
   ladowania modulow. Bez tego DataTables nadpisuje inline-style'em nasze
   CSS-owe szerokosci kolumn. */
$( document ).on( 'preInit.dt', function ( e, settings ) {
    settings.oFeatures.bAutoWidth = false;

    if ( !$( settings.nTable ).hasClass( 'cargoDynamicTable' ) ) { return; }

    /* Usun separatory tysiecy z kolumn roku (dodawane przez formatNum w EN locale).
       Lapi "1,888" / "1 888" / "1 888" -- zwraca "1888". */
    var stripThousands = function ( data ) {
        if ( !data && data !== 0 ) { return data; }
        return String( data ).replace( /^(\d{1,3})[,   ](\d{3})$/, '$1$2' );
    };

    settings.aoColumnDefs = settings.aoColumnDefs || [];
    settings.aoColumnDefs.push( {
        targets: [ 3, 4 ],
        render: stripThousands
    } );
} );

/* -- Skracanie streszczenia -------------------------------------------------
   Kolumna streszczenie = zawsze td:nth-child(2) w kazdym LawRus cargo query.
   Przypisanie do window.lawrusTruncateAll pozwala wywolac ja z konsoli. */

window.lawrusTruncateAll = function () {
    $( 'table.cargoDynamicTable tbody tr' ).each( function () {
        var $td = $( this ).children( 'td' ).eq( 1 );
        if ( !$td.length )                          { return; }
        if ( $td.find( '.lawrus-short' ).length )   { return; }
        var text = $td.text().replace( /\s+/g, ' ' ).trim();
        if ( text.length <= 100 )                   { return; }
        var e = function ( s ) {
            return s.replace( /&/g, '&amp;' )
                    .replace( /</g,  '&lt;'  )
                    .replace( />/g,  '&gt;'  );
        };
        $td.html(
            '<span class="lawrus-short">' +
                e( text.substring( 0, 100 ) ) +
                '<a href="#" class="lawrus-more" style="color:#3366cc;cursor:pointer;margin-left:2px">&#x2026;</a>' +
            '</span>' +
            '<span class="lawrus-full-wrap" style="display:none">' +
                e( text ) +
                '&#160;<a href="#" class="lawrus-less" style="color:#3366cc;cursor:pointer">&#x2191;</a>' +
            '</span>'
        );
    } );
};

/* draw.dt -- kazdy redraw DataTables (init, sort, filtr, paginacja) */
$( document ).on( 'draw.dt', window.lawrusTruncateAll );

/* Klikniecia -- delegowane, niezalezne od DataTables */
$( document ).on( 'click', '.lawrus-more', function ( e ) {
    e.preventDefault();
    var $td = $( this ).closest( 'td' );
    $td.find( '.lawrus-short' ).hide();
    $td.find( '.lawrus-full-wrap' ).show();
} );
$( document ).on( 'click', '.lawrus-less', function ( e ) {
    e.preventDefault();
    var $td = $( this ).closest( 'td' );
    $td.find( '.lawrus-full-wrap' ).hide();
    $td.find( '.lawrus-short' ).show();
} );

/* window.load -- odpala po zaladowaniu WSZYSTKICH zasobow strony (DataTables
   na pewno gotowe do tego momentu). */
$( window ).on( 'load', function () {
    window.lawrusTruncateAll();
    setTimeout( window.lawrusTruncateAll, 300 );
} );

function lawrusInitPdfButton() {
    var $entry = $( '.wpis-kroniki' );
    if ( !$entry.length ) return;
    var $actions = $entry.find( '.lawrus-actions' );
    if ( !$actions.length ) return;

    var $btn = $( '<button class="lawrus-pdf-btn" type="button">↓ Pobierz PDF</button>' );
    $btn.on( 'click', function () {
        window.print();
    } );
    $actions.append( $btn );
}

/* -- Lightbox dla zalacznikow graficznych -----------------------------------
   Klik w miniaturke zalacznika (obraz) pokazuje pelny obraz w nakladce NA
   stronie wpisu, zamiast nawigowac do pliku. Zamkniecie: X / klik w tlo / Esc
   — uzytkownik wraca dokladnie na wpis. Bez JS link dziala normalnie (href to
   bezposredni URL pliku, ustawiony przez link={{filepath:}} w szablonie). */
function lawrusInitAttachmentLightbox() {
    $( document ).on( 'click', '.lawrus-attachment-img a', function ( e ) {
        var href = $( this ).attr( 'href' );
        if ( !href ) return;
        e.preventDefault();

        var alt = $( this ).find( 'img' ).attr( 'alt' ) || '';
        var $overlay = $(
            '<div class="lawrus-lightbox-overlay" role="dialog" aria-modal="true" aria-label="Podgląd zdjęcia / Image preview">' +
              '<button type="button" class="lawrus-lightbox-close" aria-label="Zamknij / Close">×</button>' +
              '<img class="lawrus-lightbox-img" alt="">' +
            '</div>'
        );
        $overlay.find( '.lawrus-lightbox-img' ).attr( { src: href, alt: alt } );

        function close() {
            $overlay.remove();
            $( document ).off( 'keydown.lawrusLightbox' );
        }
        // Klik w tlo lub w przycisk X zamyka; klik w sam obraz — nie.
        $overlay.on( 'click', function ( ev ) {
            if ( ev.target === this || $( ev.target ).hasClass( 'lawrus-lightbox-close' ) ) {
                close();
            }
        } );
        $( document ).on( 'keydown.lawrusLightbox', function ( ev ) {
            if ( ev.key === 'Escape' || ev.keyCode === 27 ) {
                close();
            }
        } );

        $( document.body ).append( $overlay );
    } );
}

/* -- Okienko wyboru jezyka (pierwsza wizyta) --------------------------------
   Cookie lawrus_lang = pl|en. Czytane TEZ po stronie serwera (LocalSettings.php,
   hook BeforeInitialize) zeby przy kolejnych wejsciach od razu serwowac strone
   w wybranym jezyku interfejsu (uselang). Surowa nazwa cookie, bez prefiksu MW. */

function lawrusGetCookie( name ) {
    var m = document.cookie.match( '(?:^|; )' + name + '=([^;]*)' );
    return m ? decodeURIComponent( m[ 1 ] ) : null;
}

function lawrusSetLangCookie( lang, persist ) {
    var c = 'lawrus_lang=' + lang + '; path=/; SameSite=Lax';
    if ( persist ) { c += '; max-age=' + ( 60 * 60 * 24 * 365 ); } // 1 rok
    // bez max-age -> cookie sesyjne: zapyta przy nastepnym otwarciu przegladarki
    if ( window.location.protocol === 'https:' ) { c += '; Secure'; }
    document.cookie = c;
}

/* Jawny ?uselang=en|pl w URL = swiadomy wybor jezyka (przycisk LangSwitch na
   stronach statycznych/Katalog, przelacznik PL/EN we wpisie i w drilldownie).
   Zapisujemy go w cookie lawrus_lang, zeby serwer (BeforeInitialize) utrzymal ten
   jezyk takze przy kolejnej nawigacji -- inaczej menu MediaWiki (Nawigacja, Strona
   glowna...) wracaloby do PL po kliknieciu w pasek boczny (linki sidebara nie nosza
   uselang). Cookie roczne -- wybor jest swiadomy. Dziala dla anonimow (zalogowani
   maja jezyk w preferencjach konta). Po zapisie tlumi tez okienko pierwszej wizyty. */
function lawrusSyncLangCookie() {
    var ul = new URL( window.location.href ).searchParams.get( 'uselang' );
    if ( ul !== 'en' && ul !== 'pl' ) { return; }
    if ( lawrusGetCookie( 'lawrus_lang' ) === ul ) { return; }
    lawrusSetLangCookie( ul, true );
}

function lawrusInitLangPrompt() {
    // wgUserId jest null dla anonimow, liczba dla zalogowanych (ci maja
    // jezyk w preferencjach konta, nie pokazujemy im okienka).
    if ( mw.config.get( 'wgUserId' ) ) { return; }
    if ( lawrusGetCookie( 'lawrus_lang' ) ) { return; } // wybor juz zapisany

    var $overlay = $(
        '<div class="lawrus-lang-modal-overlay" role="dialog" aria-modal="true" aria-label="Choose language / Wybierz jezyk">' +
          '<div class="lawrus-lang-modal">' +
            '<p class="lawrus-lang-modal-title">Choose your language<br>Wybierz język</p>' +
            '<div class="lawrus-lang-modal-buttons">' +
              '<button type="button" data-lang="en">Continue in English</button>' +
              '<button type="button" data-lang="pl">Kontynuj po polsku</button>' +
            '</div>' +
            '<label class="lawrus-lang-modal-remember">' +
              '<input type="checkbox" id="lawrus-lang-remember"> ' +
              'Don’t show this message again / Nie pokazuj więcej tego komunikatu' +
            '</label>' +
          '</div>' +
        '</div>'
    );

    $overlay.on( 'click', 'button[data-lang]', function () {
        var lang    = $( this ).data( 'lang' );
        var persist = $overlay.find( '#lawrus-lang-remember' ).prop( 'checked' );
        lawrusSetLangCookie( lang, persist );

        // Zapisalismy preferencje w cookie -> przeladowujemy bez jawnego uselang,
        // zeby zadecydowal serwer (LocalSettings.php): dla EN ustawi interfejs
        // angielski I przekieruje polska strone statyczna na bliźniaka EN
        // (Strona główna -> Main Page itd.) -- dokladnie jak przycisk PL/EN.
        var url = new URL( window.location.href );
        url.searchParams.delete( 'uselang' );
        if ( url.toString() === window.location.href ) {
            // URL bez zmian (np. PL na stronie PL) -> serwer i tak nic nie zmieni,
            // wystarczy zamknac okienko. Dla EN URL tez sie nie zmieni, ale
            // przeladowanie jest potrzebne by serwer zadzialal -> wymuszamy nizej.
            if ( lang === 'en' ) {
                window.location.reload();
            } else {
                $overlay.remove();
            }
        } else {
            window.location.href = url.toString();
        }
    } );

    $( document.body ).append( $overlay );
}

function lawrusInitLangSwitch() {
    var $entries = $( '.wpis-kroniki' );
    if ( !$entries.length ) return;

    var isEn = ( mw.config.get( 'wgUserLanguage' ) === 'en' );

    $entries.each( function () {
        var $entry = $( this );
        if ( !$entry.find( '.lawrus-lang-pl, .lawrus-lang-en' ).length ) return;

        if ( isEn ) {
            $entry.addClass( 'lang-en' );
        }

        var $sw = $(
            '<div class="lawrus-lang-switch">' +
            '<button data-lang="pl"' + ( !isEn ? ' class="active"' : '' ) + '>PL</button>' +
            '<button data-lang="en"' + ( isEn ? ' class="active"' : '' ) + '>EN</button>' +
            '</div>'
        );
        $entry.prepend( $sw );

        $sw.on( 'click', 'button', function () {
            var lang = $( this ).data( 'lang' );
            var url = new URL( window.location.href );
            // Jawny uselang (takze 'pl') zostawiamy w URL, zeby wygral z cookie
            // preferencji ustawionym przez okienko wyboru jezyka.
            url.searchParams.set( 'uselang', lang === 'en' ? 'en' : 'pl' );
            window.location.href = url.toString();
        } );
    } );
}

function lawrusInitDrilldownLangSwitch() {
    if ( mw.config.get( 'wgCanonicalSpecialPageName' ) !== 'Drilldown' ) return;

    var isEn = ( mw.config.get( 'wgUserLanguage' ) === 'en' );
    var $sw = $(
        '<div class="lawrus-lang-switch" style="margin-bottom:1em;">' +
        '<button data-lang="pl"' + ( !isEn ? ' class="active"' : '' ) + '>PL</button>' +
        '<button data-lang="en"' + ( isEn ? ' class="active"' : '' ) + '>EN</button>' +
        '</div>'
    );

    $( '#mw-content-text' ).prepend( $sw );

    $sw.on( 'click', 'button', function () {
        var lang = $( this ).data( 'lang' );
        var url = new URL( window.location.href );
        // Jawny uselang (takze 'pl') wygrywa z cookie preferencji jezyka.
        url.searchParams.set( 'uselang', lang === 'en' ? 'en' : 'pl' );
        window.location.href = url.toString();
    } );
}

/* -- Dodawanie wpisu: duplikat nazwy + propozycja nowej nazwy ----------------
   Na Specjalna:FormStart przed wysłaniem sprawdza, czy wpis o tej nazwie już
   istnieje. Jeśli tak — pyta: otworzyć istniejący czy utworzyć nowy. Dla nowego
   proponuje nazwę (wiodąca liczba +1, a gdy brak liczby — prefiks "0_") i wstawia
   ją do pola JAKO PROPOZYCJĘ — użytkownik może ją zmienić; nic nie dzieje się
   automatycznie. */

function lawrusEscapeRegex( s ) {
    return s.replace( /[.*+?^${}()|[\]\\]/g, '\\$&' );
}

/* Propozycja nazwy nowego wpisu na podstawie nazwy wpisanej i listy wszystkich
   tytułów (NS_MAIN). Reguły:
   - nazwa bez wiodącej liczby (np. "test") lub z prefiksem licznika i spacją
     (stare "0 test"): baza = nazwa bez prefiksu; szukamy najwyższego istniejącego
     "N-baza"/"N baza" i proponujemy (max+1)-baza (gdy brak — "0-baza"). Dzięki
     temu przy istniejących "test" i "0-test" propozycją jest "1-test".
     Separator dodajemy jako MYŚLNIK "-" — MediaWiki NIE zamienia go na spację
     (w przeciwieństwie do "_"), więc pozostaje widoczny w tytule.
   - nazwa "rocznikowa"/z wiodącą liczbą (np. "1022-Rus-Prawda", "0-test"):
     wiodąca liczba +1 (liczba znacząca = rok lub kolejny numer). */
function lawrusProposeEntryName( name, titles ) {
    // MediaWiki traktuje "_" i spację w tytułach równoważnie i zwraca formę ze
    // spacjami (allpages: "0_test" -> "0 test"). Normalizujemy "_" do spacji.
    var norm = function ( s ) { return String( s ).replace( /_/g, ' ' ).trim(); };
    var nName = norm( name );

    var base;
    var pm = nName.match( /^\d+\s+([\s\S]+)$/ ); // stara forma "N baza" (separator: spacja/_)
    if ( pm ) {
        base = pm[ 1 ];
    } else if ( /^\d/.test( nName ) ) {
        // wiodąca liczba (rocznik "1022-Rus-Prawda" lub licznik "0-test") -> +1
        var ym = nName.match( /^(\d+)([\s\S]*)$/ );
        return ( parseInt( ym[ 1 ], 10 ) + 1 ) + ym[ 2 ];
    } else {
        base = nName;
    }
    // Najwyższe istniejące "N-baza" / "N baza" (separator: myślnik lub spacja/_).
    var re = new RegExp( '^(\\d+)[\\s-]+' + lawrusEscapeRegex( base ) + '$', 'i' );
    var max = -1;
    ( titles || [] ).forEach( function ( t ) {
        var m = norm( t ).match( re );
        if ( m ) { max = Math.max( max, parseInt( m[ 1 ], 10 ) ); }
    } );
    return ( max + 1 ) + '-' + base;
}

function lawrusInitFormStartGuard() {
    if ( mw.config.get( 'wgCanonicalSpecialPageName' ) !== 'FormStart' ) { return; }

    var $form = $( '.pfFormInputWrapper' ).closest( 'form' );
    if ( !$form.length ) { return; }

    function askDuplicate( name, $input ) {
        var safe = mw.html.escape( name );
        var $overlay = $(
            '<div class="lawrus-lang-modal-overlay" role="dialog" aria-modal="true">' +
              '<div class="lawrus-lang-modal">' +
                '<p class="lawrus-lang-modal-title">' +
                  'Wpis „' + safe + '” już istnieje.<br>' +
                  'An entry named “' + safe + '” already exists.' +
                '</p>' +
                '<div class="lawrus-lang-modal-buttons">' +
                  '<button type="button" data-act="existing">Otwórz istniejący / Open existing</button>' +
                  '<button type="button" data-act="new">Utwórz nowy / Create new</button>' +
                '</div>' +
                '<label class="lawrus-lang-modal-remember">' +
                  '<a href="#" data-act="cancel">Anuluj / Cancel</a>' +
                '</label>' +
              '</div>' +
            '</div>'
        );
        $overlay.on( 'click', '[data-act]', function ( e ) {
            e.preventDefault();
            var act = $( this ).data( 'act' );
            $overlay.remove();
            if ( act === 'existing' ) {
                $form[ 0 ].submit(); // natywny submit pomija ten handler -> edycja istniejącego
            } else if ( act === 'new' ) {
                // tylko propozycja — pobierz wszystkie tytuły, policz rodzeństwo
                // "N_baza" i wstaw propozycję do pola (użytkownik może ją zmienić).
                var applyProposal = function ( titles ) {
                    $input.val( lawrusProposeEntryName( name, titles ) )
                        .trigger( 'input' ).trigger( 'change' ).trigger( 'focus' );
                };
                new mw.Api().get( {
                    action: 'query', list: 'allpages', apnamespace: 0,
                    aplimit: 'max', formatversion: 2
                } ).done( function ( d ) {
                    var titles = ( ( d.query && d.query.allpages ) || [] )
                        .map( function ( p ) { return p.title; } );
                    applyProposal( titles );
                } ).fail( function () {
                    applyProposal( [] );
                } );
            }
        } );
        $( document.body ).append( $overlay );
    }

    $form.on( 'submit', function ( e ) {
        var $input = $form.find( 'input[name="page_name"]' );
        var name = $.trim( $input.val() || '' );
        if ( name === '' ) { return; } // puste — niech obsłuży PHP

        e.preventDefault();
        mw.loader.using( 'mediawiki.api' ).done( function () {
            new mw.Api().get( {
                action: 'query', titles: name, formatversion: 2
            } ).done( function ( data ) {
                var pages = ( data.query && data.query.pages ) || [];
                var exists = pages.length && !pages[ 0 ].missing && !pages[ 0 ].invalid;
                if ( exists ) {
                    askDuplicate( name, $input );
                } else {
                    $form[ 0 ].submit(); // wolna nazwa — twórz nowy
                }
            } ).fail( function () {
                $form[ 0 ].submit(); // błąd API — nie blokuj użytkownika
            } );
        } );
    } );
}

function lawrusReorderParticipants() {
    /* PageForms wymaga, zeby bloki {{{for template|...|multiple}}} byly poza
       glownym szablonem -- przez to div#lawrus-uczestnicy-* laduje na dole
       formularza. Przenosimy je bezposrednio pod odpowiednie sekcje 3/4.
       Uzywamy detach()+insertAfter() zeby zachowac event-handlery jQuery.
       v2 -- wywolanie przez setTimeout(0) gwarantuje uruchomienie po PageForms. */
    var $sec3    = $( '#lawrus-sec3' );
    var $sec4    = $( '#lawrus-sec4' );
    var $krajowa = $( '#lawrus-uczestnicy-krajowa' );
    var $miedz   = $( '#lawrus-uczestnicy-miedz' );

    if ( $sec3.length && $krajowa.length ) {
        $krajowa.detach().insertAfter( $sec3 );
    }
    if ( $sec4.length && $miedz.length ) {
        $miedz.detach().insertAfter( $sec4 );
    }
}

/* -- LawRusWorkflow: CSRF token injection ------------------------------------
   Wypelnia pole wptoken w formularzach .lawrus-action-form tokenem sesji.
   Musi dzialac przed wyslaniem formularza -- ladowanie synchroniczne z mw.loader. */
mw.loader.using( 'mediawiki.user' ).done( function () {
    var token = mw.user.tokens.get( 'csrfToken' );
    $( document ).on( 'submit', '.lawrus-action-form', function () {
        $( this ).find( 'input[name="wptoken"]' ).val( token );
    } );
    // Wypelnij od razu przy ladowaniu (dla formularzy juz widocznych)
    $( '.lawrus-action-form input[name="wptoken"]' ).val( token );
} );

$( function () {

    console.log( '[LawRus] Common.js: document.ready, ns=' + mw.config.get( 'wgNamespaceNumber' ) );

    lawrusSyncLangCookie();
    lawrusInitLangPrompt();
    lawrusInitFormStartGuard();
    lawrusInitPdfButton();
    lawrusInitAttachmentLightbox();
    lawrusInitLangSwitch();
    lawrusInitDrilldownLangSwitch();

    if ( mw.config.get( 'wgUserName' ) ) {
        $( 'body' ).addClass( 'lawrus-zalogowany' );
    }

    if ( $( '#lawrus-uczestnicy-krajowa' ).length ) {
        setTimeout( lawrusReorderParticipants, 0 );
    }

    /* Timeouty -- pokrywaja rozne scenariusze async ladowania Cargo/DataTables */
    window.lawrusTruncateAll();
    setTimeout( window.lawrusTruncateAll, 300 );
    setTimeout( window.lawrusTruncateAll, 800 );
    setTimeout( window.lawrusTruncateAll, 2000 );

    /* -- Walidacja przed zapisem (tylko formularz WpisKroniki) -- */
    var $form = $( '#pfForm' );
    if ( !$form.length ) return;

    var REQUIRED_AT_4 = [
        { id: 'input_datacja_rok_lacinski', label: 'Rok AD'          },
        { id: 'input_jezyk_oryginalu',      label: 'Jezyk oryginalu'  },
        { id: 'input_kraj',                 label: 'Kraj'             },
        { id: 'input_tekst_zrodlowy',       label: 'Tekst zrodlowy'   },
        { id: 'input_bibliografia',         label: 'Bibliografia'     },
        { id: 'input_streszczenie_pl',      label: 'Streszczenie PL'  },
        { id: 'input_streszczenie_en',      label: 'Streszczenie EN'  },
        { id: 'input_tlumaczenie_pl',       label: 'Tlumaczenie PL'   },
        { id: 'input_tlumaczenie_en',       label: 'Tlumaczenie EN'   }
    ];
    var HIGH_STATUS = [ '4-do-publikacji', '5-opublikowane' ];

    $form.on( 'submit', function ( e ) {
        var status = $( '#input_status_wpisu' ).val() || '';
        if ( HIGH_STATUS.indexOf( status ) === -1 ) return;

        var missing = REQUIRED_AT_4.filter( function ( f ) {
            return ( $( '#' + f.id ).val() || '' ).trim() === '';
        } );

        if ( $( '[id^="input_typ_wpisu_"]:checked' ).length === 0 ) {
            missing.push( { id: null, label: 'Typ wpisu' } );
        }

        if ( missing.length === 0 ) return;

        e.preventDefault();
        var names = missing.map( function ( f ) { return f.label; } ).join( ', ' );
        mw.loader.using( 'mediawiki.notification' ).then( function () {
            mw.notify(
                'Przy statusie "' + status + '" wymagane sa: ' + names + '. Uzupelnij przed zapisem.',
                { type: 'error', autoHide: false, tag: 'lawrus-validation' }
            );
        } );
        missing.forEach( function ( f ) {
            if ( f.id ) { $( '#' + f.id ).css( 'border-color', '#d33' ); }
        } );
        missing.forEach( function ( f ) {
            if ( f.id ) {
                $( '#' + f.id ).one( 'input', function () {
                    $( this ).css( 'border-color', '' );
                } );
            }
        } );
    } );

} );