MediaWiki:Common.js: Perbedaan antara revisi

Tidak ada ringkasan suntingan
Tanda: Suntingan perangkat seluler Suntingan peramban seluler
Tidak ada ringkasan suntingan
Tanda: Suntingan perangkat seluler Suntingan peramban seluler
 
(32 revisi perantara oleh pengguna yang sama tidak ditampilkan)
Baris 1: Baris 1:
/* ==========================================================
/* ==========================================================
   🧠 MIPPEDIA DATA - SMART CONTEXT + AUTO THUMBNAIL
  ⚡ MIPPEDIA SPEED OPTIMIZER - PURE SPEED & STABLE (V1.1)
   Features: Smart Sync, Auto-Age, Auto-Bold, Smart Links,
  Features: Instant Link Pre-fetching Engine (No Animation/Pure Performance).
             Dynamic Context, AND NEW: Auto Thumbnail Image
  Optimization: Zero-flicker, scoped lifecycle protection, advanced anti-cache clutter.
   ========================================================== */
  Target: Built for maximum long-term stability and bulletproof caching.
  ========================================================= */
 
(function() {
    // Ambil nama host secara dinamis (contoh: data.mippedia.org)
    var currentHost = window.location.hostname;
 
    // --- 🗲 ENGINE PRE-FETCH CORES ---
    function injectPrefetchLink(url) {
        // Jaring Pengaman 1: Jangan injeksi jika link prefetch untuk URL ini sudah ada di head browser
        if (document.querySelector('link[href="' + url + '"]')) return;
 
        var prefetcher = document.createElement('link');
        prefetcher.rel = 'prefetch';
        prefetcher.href = url;
       
        // Jaring Pengaman 2: Failsafe anti-eror cache jika network mendadak sibuk
        prefetcher.onerror = function() { prefetcher.remove(); };
       
        document.head.appendChild(prefetcher);
    }
 
    function initPrefetchDispatch(href) {
        // Memanfaatkan requestIdleCallback agar proses prefetch hanya berjalan saat browser senggang
        // sehingga tidak akan membuat scrolling halaman terasa berat/patah-patah
        if (window.requestIdleCallback) {
            window.requestIdleCallback(function() { injectPrefetchLink(href); });
        } else {
            setTimeout(function() { injectPrefetchLink(href); }, 20);
        }
    }
 
    // --- 🎮 COGNITIVE EVENT LISTENERS (UNIVERSAL HOVER & TOUCH) ---
    // Mendengarkan area konten utama MediaWiki (Desktop & Tampilan Mobile HP)
    $(document).on('mouseenter pointerdown', '#mw-content-text a, .mw-body-content a, #bodyContent a, .mw-parser-output a', function() {
        var $link = $(this);
        var href = $link.attr('href');
 
        // --- 🛡️ FILTER STRATA TINGGI: ABREVIASI LINK BERGUNA (ANTI CRASH CACHE) ---
        // 1. Abaikan link kosong, javascript void, atau buka tab baru (_blank)
        if (!href || href.startsWith('#') || href.startsWith('javascript:') || $link.attr('target') === '_blank') return;
       
        // 2. Abaikan link merah (artikel belum ada), link sunting teks, riwayat versi, dan aksi form MediaWiki
        if ($link.hasClass('new') || href.includes('action=') || href.includes('diff=') || href.includes('oldid=')) return;
       
        // 3. Abaikan link perkakas krusial pengurus / sistem otentikasi (hindari prefetch logout tidak sengaja)
        if (href.includes('Istimewa:') || href.includes('Special:') || href.includes('do=logout')) return;
 
        // Pastikan hanya mengeksekusi routing internal ekosistem Mippedia
        var isInternal = href.indexOf(currentHost) !== -1 || href.startsWith('/') || !href.includes('://');
        if (isInternal) {
            initPrefetchDispatch(href);
        }
    });
})();
 
/* ==========================================================
  🌐 MIPPEDIA TAB ROUTER INTEGRATOR - UNIVERSAL TRANSITION (V1.2)
  Features: Pure Pre-Navigation Routing Simulation on Tab Title.
  Optimization: Universal trigger for any navigation, 0% page-load flicker.
  Target: Clean, lightweight, long-term safety.
  ========================================================= */
 
(function() {
    // Ambil nama host secara dinamis (contoh: data.mippedia.org)
    var currentHost = window.location.hostname;
    var routingTimeout = null;
    var rawOriginalTitle = document.title;
 
    // --- ENGINE CORE: UBAH JUDUL JADI DOMAIN ---
    function executeUniversalRouting() {
        if (routingTimeout) clearTimeout(routingTimeout);
       
        // Tangkap judul asli terupdate sebagai jaring pengaman
        var liveTitle = document.title;
        if (liveTitle !== currentHost) {
            rawOriginalTitle = liveTitle;
        }
 
        // SET DOMAIN DI AWAL (Saat aksi diklik / sebelum pindah halaman)
        document.title = currentHost;
 
        // Failsafe: Jika dalam 3 detik halaman tidak jadi pindah (batal/koneksi drop),
        // kembalikan ke judul semula agar tidak tersangkut.
        routingTimeout = setTimeout(function() {
            if (document.title === currentHost) {
                document.title = rawOriginalTitle;
            }
        }, 3000);
    }
 
    // --- ⚡ PEMICU 1: UNIVERSAL CLICK CONTROLLER (APAPUN YANG DIKLIK) ---
    // Menggunakan Vanilla JS Event Listener di tingkat window agar mencakup seluruh elemen tanpa tebang pilih
    window.addEventListener('click', function(e) {
        // Cari tahu apakah elemen yang diklik berada di dalam tag <a> (Link)
        var anchor = e.target.closest('a');
       
        if (anchor) {
            var href = anchor.getAttribute('href');
            var target = anchor.getAttribute('target');
 
            // Abaikan jika link kosong, link jangkar internal halaman (#), atau buka di tab baru (_blank)
            if (!href || href.startsWith('#') || href.startsWith('javascript:') || target === '_blank') {
                return;
            }
           
            // Eksekusi jika perpindahan terjadi di domain yang sama (internal routing)
            var isInternal = href.indexOf(currentHost) !== -1 || href.startsWith('/') || !href.includes('://');
            if (isInternal) {
                executeUniversalRouting();
            }
        } else {
            // JIKA YANG DIKLIK BUKAN LINK (Tapi tombol biasa, widget, atau elemen aksi pemindah halaman lainnya)
            // Kita cek apakah elemen tersebut memiliki indikasi memicu fungsi perpindahan/loading
            var elementTag = e.target.tagName.toLowerCase();
            if (elementTag === 'button' || e.target.closest('button') || e.target.getAttribute('role') === 'button') {
                executeUniversalRouting();
            }
        }
    }, { passive: true });
 
    // --- 💾 PEMICU 2: UNIVERSAL SUBMIT CONTROLLER (SIMPAN/FORM) ---
    // Memicu efek domain saat submit form (Simpan halaman, cari artikel, dll)
    window.addEventListener('submit', function() {
        executeUniversalRouting();
    });
 
    // --- 🛡️ SAFETY GUARDIAN: ANTI STUCK ON TAB BLUR ---
    // Jika user buru-buru pindah tab sebelum halaman sempat berganti, kembalikan judul asli
    window.addEventListener('blur', function() {
        if (routingTimeout) clearTimeout(routingTimeout);
        if (document.title === currentHost) {
            document.title = rawOriginalTitle;
        }
    });
    window.addEventListener('pagehide', function() {
        if (routingTimeout) clearTimeout(routingTimeout);
        if (document.title === currentHost) {
            document.title = rawOriginalTitle;
        }
    });
})();
 
/* ==========================================================
   🧠 MIPPEDIA DATA - SMART CONTEXT + AUTO THUMBNAIL (V10.1)
   Features: Priority Check (ID > EN > CONCISE), Auto-Age,  
            Auto-Bold, Smart Links, Dynamic Context,
             Targeted Isolation (Safe for Forms), Dynamic Noindex,
            Aesthetic Centered Empty State + 🚀 Instant Action Hub (3 Buttons)
  Fixes: Mobile View Visibility & Custom Short URL Route (No /w/)
   ========================================================= */
(function() {
(function() {
     $(document).ready(function() {
     $(document).ready(function() {
         var $descSection = $('#mip-desc-section'), $descBox = $('#mip-auto-description'),
         var $descSection = $('#mip-desc-section'), $descBox = $('#mip-auto-description'),
             $sourceInfo = $('#mip-source-info'), $portalLinks = $('#mip-portal-links'),
             $sourceInfo = $('#mip-source-info'), $portalLinks = $('#mip-portal-links'),
             $projectPortal = $('#mip-project-portal');
             $projectPortal = $('#mip-project-portal'), $mainContent = $('#mw-content-text');
          
          
         if (!$descBox.length) return;
         if (!$descBox.length) return;
Baris 16: Baris 166:
             currentYear = 2026;
             currentYear = 2026;


        // --- KONFIGURASI PROYEK ---
         var projects = [
         var projects = [
             { id: 'id', name: 'Mippedia bahasa Indonesia', url: 'https://id.mippedia.org/api.php', base: 'https://id.mippedia.org/wiki/', label: 'Bahasa Indonesia' },
             { id: 'id', name: 'Mippedia bahasa Indonesia', url: 'https://id.mippedia.org/api.php', base: 'https://id.mippedia.org/wiki/', label: 'Mippedia Indonesia' },
             { id: 'en', name: 'Mippedia bahasa Inggris', url: 'https://en.mippedia.org/api.php', base: 'https://en.mippedia.org/wiki/', label: 'Bahasa Inggris' },
             { id: 'en', name: 'Mippedia bahasa Inggris', url: 'https://en.mippedia.org/api.php', base: 'https://en.mippedia.org/wiki/', label: 'Mippedia Inggris' },
             { id: 'concise', name: 'Mippedia bahasa Indonesia ringkas', url: 'https://concise.mippedia.org/api.php', base: 'https://concise.mippedia.org/wiki/', label: 'Versi Ringkas' }
             { id: 'concise', name: 'Mippedia bahasa Indonesia ringkas', url: 'https://concise.mippedia.org/api.php', base: 'https://concise.mippedia.org/wiki/', label: 'Mippedia Ringkas' }
         ];
         ];


         // --- SEMUA FUNGSI AWAL (TETAP UTUH) ---
         // --- 🎨 ANIMASI, HOVER EFFECT & RESPONSIVE STYLE ---
        if (!$('#mip-clean-style').length) {
            $('head').append(
                '<style id="mip-clean-style">' +
                    '@keyframes mipFadeInUp {' +
                        'from { opacity: 0; transform: translateY(15px); }' +
                        'to { opacity: 1; transform: translateY(0); }' +
                    '}' +
                    '#mip-empty-state-notice {' +
                        'display: block !important;' + /* Memaksa tampil di seluler/mobile */
                        'animation: mipFadeInUp 0.6s cubic-bezier(0.16, 1, 0.3, 1) forwards;' +
                    '}' +
                    '.mip-btn-container {' +
                        'display: flex;' +
                        'flex-wrap: wrap;' + /* Supaya otomatis turun ke bawah kalau di HP */
                        'justify-content: center;' +
                        'margin-bottom: 30px;' +
                        'padding-top: 5px;' +
                    '}' +
                    '.mip-action-btn {' +
                        'display: inline-flex; align-items: center; justify-content: center;' +
                        'padding: 10px 18px; margin: 6px; font-size: 0.88em; font-weight: 600;' +
                        'color: #6a5acd !important; background: #ffffff; border: 1.5px solid #6a5acd;' +
                        'border-radius: 30px; text-decoration: none !important; cursor: pointer;' +
                        'transition: all 0.25s ease-in-out; box-shadow: 0 2px 5px rgba(106,92,205,0.08);' +
                        'flex-grow: 1; max-width: 220px; min-width: 150px;' + /* Penyesuaian porsi tombol di HP */
                    '}' +
                    '.mip-action-btn:hover {' +
                        'color: #ffffff !important; background: #6a5acd;' +
                        'transform: translateY(-2px); box-shadow: 0 4px 12px rgba(106,92,205,0.2);' +
                    '}' +
                    '.mip-action-btn:active {' +
                        'transform: translateY(0);' +
                    '}' +
                '</style>'
            );
        }
 
        // --- FUNGSI TOOLS ---
         function applyAutoBold(text) { return text.replace(new RegExp('(' + pageTitle + ')', 'gi'), '<strong>$1</strong>'); }
         function applyAutoBold(text) { return text.replace(new RegExp('(' + pageTitle + ')', 'gi'), '<strong>$1</strong>'); }
         function cleanExtract(text) { return text.replace(/\[\d+\]/g, '').replace(/\{\{[^}]+\}\}/g, '').replace(/\(\s*\)/g, '').replace(/\s\s+/g, ' ').trim(); }
         function cleanExtract(text) { return text.replace(/\[\d+\]/g, '').replace(/\{\{[^}]+\}\}/g, '').replace(/\(\s*\)/g, '').replace(/\s\s+/g, ' ').trim(); }
Baris 31: Baris 220:
             var bulan = ["Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember"];
             var bulan = ["Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember"];
             var tglSkrg = hari[date.getDay()] + ", " + date.getDate() + " " + bulan[date.getMonth()] + " " + date.getFullYear();
             var tglSkrg = hari[date.getDay()] + ", " + date.getDate() + " " + bulan[date.getMonth()] + " " + date.getFullYear();
 
             return text.replace(/(hari ini|saat ini|sekarang)/gi, '$1 (' + tglSkrg + ')')
             return text
                .replace(/(hari ini|saat ini|sekarang)/gi, '$1 (' + tglSkrg + ')')
                 .replace(/(\d{1,2}\s(?:Januari|Februari|Maret|April|Mei|Juni|Juli|Agustus|September|Oktober|November|Desember)\s(\d{4}))/gi, function(m, f, y) {  
                 .replace(/(\d{1,2}\s(?:Januari|Februari|Maret|April|Mei|Juni|Juli|Agustus|September|Oktober|November|Desember)\s(\d{4}))/gi, function(m, f, y) {  
                     return f + " <span style='color: #444; font-weight: bold;'>– usia " + (currentYear - parseInt(y)) + " tahun</span>";  
                     return f + " <span style='color: #444; font-weight: bold;'>– usia " + (currentYear - parseInt(y)) + " tahun</span>";  
Baris 50: Baris 237:
         }
         }


         function buildSmartLinks(currentId) {
        // --- SMART LINKS PORTAL ---
         function buildSmartLinks(currentId, validatedEnTitle) {
             $portalLinks.empty();
             $portalLinks.empty();
             var validLinks = [];
             var validLinks = [];
           
             var checkRequests = projects.filter(p => p.id !== currentId).map(function(p) {
             var checkRequests = projects.filter(p => p.id !== currentId).map(function(p) {
                 return $.ajax({ url: p.url, data: { action: 'query', titles: pageTitle, format: 'json', origin: '*' }, dataType: 'json' })
                var targetTitle = (p.id === 'en' && validatedEnTitle) ? validatedEnTitle : pageTitle;
                     .then(function(data) { if (data.query.pages["-1"] === undefined) { validLinks.push('<a href="' + p.base + encodeURIComponent(pageTitle) + '" target="_blank" style="color: #6a5acd; text-decoration: underline; font-weight: bold;">' + p.label + '</a>'); } });
                if (p.id === 'en' && !validatedEnTitle) return $.Deferred().resolve();
 
                 return $.ajax({ url: p.url, data: { action: 'query', titles: targetTitle, format: 'json', origin: '*' }, dataType: 'json' })
                     .then(function(data) {  
                        if (data.query.pages["-1"] === undefined) {  
                            validLinks.push('<a href="' + p.base + encodeURIComponent(targetTitle) + '" target="_blank" style="color: #6a5acd; text-decoration: underline; font-weight: bold;">' + p.label + '</a>');  
                        }  
                    });
             });
             });
             $.when.apply($, checkRequests).done(function() {
             $.when.apply($, checkRequests).done(function() {
                 if (validLinks.length > 0) { $portalLinks.html(validLinks.join(' <span style="color:#ccc; margin: 0 5px;">•</span> ')); $projectPortal.fadeIn(); }
                 if (validLinks.length > 0) { $portalLinks.html(validLinks.join(' <span style="color:#ccc; margin: 0 5px;">•</span> ')); $projectPortal.fadeIn(); }
Baris 62: Baris 259:
         }
         }


         // --- SISTEM SMART CHECK DENGAN SUPPORT THUMBNAIL ---
        // --- 🧠 RENDER EMPTY STATE + INTERACTIVE HUB ---
        function toggleMippediaLayout(isEmpty) {
            var $existingNotice = $('#mip-empty-state-notice');
            var userGroups = mw.config.get('wgUserGroups') || [];
            var isSysop = userGroups.indexOf('sysop') !== -1;
 
            var $wikiDataElements = $mainContent.children().not('#mip-empty-state-notice, #mip-desc-section, script, style, .printfooter');
            var $editElements = $('#ca-edit, .mw-editsection, .wb-editsection, .mip-edit-trigger, [id*="edit"], [class*="edit-pencil"]');
            var $metaRobots = $('meta[name="robots"]');
 
            if (isEmpty) {
                if (!$existingNotice.length) {
                    // Perbaikan Jalur URL tanpa sub-folder /w/ langsung menembak Short URL root parameter
                    var createUrlID = 'https://id.mippedia.org/index.php?title=' + encodeURIComponent(pageTitle) + '&action=edit';
                    var createUrlEN = 'https://en.mippedia.org/index.php?title=' + encodeURIComponent(pageTitle) + '&action=edit';
                    var createUrlConcise = 'https://concise.mippedia.org/index.php?title=' + encodeURIComponent(pageTitle) + '&action=edit';
 
                    var noticeHtml =
                        '<div id="mip-empty-state-notice" style="text-align: center; max-width: 600px; margin: 40px auto; padding: 10px; font-family: -apple-system, BlinkMacSystemFont, \'Segoe UI\', Roboto, sans-serif;">' +
                            '<!-- Ikon Warning Besar -->' +
                            '<div style="margin-bottom: 22px; display: inline-block;">' +
                                '<svg width="64" height="64" viewBox="0 0 24 24" fill="none" stroke="#d9534f" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">' +
                                    '<path d="M10.29 3.86L1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"></path>' +
                                    '<line x1="12" y1="9" x2="12" y2="13"></line>' +
                                    '<line x1="12" y1="17" x2="12.01" y2="17"></line>' +
                                '</' + 'svg>' +
                            '</div>' +
                            '<!-- Judul Utama -->' +
                            '<h4 style="color: #2c3e50; margin: 0 0 12px 0; font-size: 1.35em; font-weight: 700; letter-spacing: -0.3px;">Item Data Struktur Tidak Memiliki Sumber Artikel</h4>' +
                            '<!-- Deskripsi Utama -->' +
                            '<p style="color: #555; margin: 0 0 25px 0; font-size: 0.98em; line-height: 1.6;">' +
                                'Item data ini saat ini <strong>tidak memiliki artikel rujukan satu pun</strong> di Mippedia Indonesia, Mippedia Inggris, maupun Mippedia Indonesia Ringkas.' +
                            '</p>' +
                           
                            '<!-- 🚀 INSTANT ACTION HUB (Flexbox-Responsive Mobile & URL No /w/) -->' +
                            '<div class="mip-btn-container">' +
                                '<a href="' + createUrlID + '" target="_blank" class="mip-action-btn">' +
                                    '<span style="margin-right: 6px;"></span>Buat Artikel ID' +
                                '</a>' +
                                '<a href="' + createUrlEN + '" target="_blank" class="mip-action-btn">' +
                                    '<span style="margin-right: 6px;"></span>Buat Artikel EN' +
                                '</a>' +
                                '<a href="' + createUrlConcise + '" target="_blank" class="mip-action-btn">' +
                                    '<span style="margin-right: 6px;"></span>Buat Artikel Ringkas' +
                                '</a>' +
                            '</div>' +
 
                            '<!-- Catatan Kaki Info Kecil -->' +
                            '<div style="font-size: 0.85em; color: #a0a0a0; line-height: 1.5; font-style: italic; border-top: 1px solid #f0f0f0; padding-top: 15px;">' +
                                'Informasi: Item data yang tidak terikat dengan artikel ensiklopedia apa pun dalam ekosistem Mippedia akan dihapus secara otomatis oleh pengurus dalam waktu dekat.' +
                            '</div>' +
                        '</div>';
                    $mainContent.prepend(noticeHtml);
                }
 
                $wikiDataElements.hide();
                $descSection.hide();
 
                if (!isSysop) {
                    $editElements.css({ 'pointer-events': 'none', 'opacity': '0.3', 'cursor': 'not-allowed' }).hide();
                    $(document).on('keydown.miplock', function(e) {
                        if (e.key.toLowerCase() === 'e' && !$(e.target).is('input, textarea')) {
                            e.preventDefault();
                            return false;
                        }
                    });
                }
 
                mw.config.set('wgRobotsPolicies', { 'index': 'noindex', 'follow': 'nofollow' });
                if ($metaRobots.length) {
                    $metaRobots.attr('content', 'noindex, nofollow');
                } else {
                    $('head').append('<meta name="robots" content="noindex, nofollow">');
                }
 
            } else {
                if ($existingNotice.length) $existingNotice.remove();
               
                $wikiDataElements.show();
                $descSection.show();
 
                if (!isSysop) {
                    $(document).off('keydown.miplock');
                }
 
                mw.config.set('wgRobotsPolicies', { 'index': 'index', 'follow': 'follow' });
                if ($metaRobots.length) {
                    $metaRobots.attr('content', 'index, follow');
                }
            }
        }
 
         // --- SISTEM PRIORITAS ---
         var cached = JSON.parse(localStorage.getItem(cacheKey) || "{}");
         var cached = JSON.parse(localStorage.getItem(cacheKey) || "{}");
        var globalEnTitle = cached.enTitle || "";


         function init() {
         function init() {
             var pCheck = projects[Math.floor(Math.random() * projects.length)];
             var pPrimary = projects[0];  
             $.ajax({
             $.ajax({
                 url: pCheck.url,
                 url: pPrimary.url,
                 data: { action: 'query', prop: 'revisions|pageimages', rvprop: 'timestamp', piprop: 'thumbnail', pithumbsize: 150, titles: pageTitle, format: 'json', origin: '*' },
                 data: { action: 'query', prop: 'revisions|pageimages|langlinks', lllang: 'en', rvprop: 'timestamp', piprop: 'thumbnail', pithumbsize: 150, titles: pageTitle, format: 'json', origin: '*' },
                 dataType: 'json',
                 dataType: 'json',
                 success: function(res) {
                 success: function(res) {
                     var pg = res.query.pages, id = Object.keys(pg)[0];
                     var pg = res.query.pages, id = Object.keys(pg)[0];
                     var latestTS = (id != "-1") ? pg[id].revisions[0].timestamp : "0";
                     var latestTS = (id != "-1") ? pg[id].revisions[0].timestamp : "0";
                    // Ambil thumbnail kalau ada
                     var thumbUrl = (id != "-1" && pg[id].thumbnail) ? pg[id].thumbnail.source : "";
                     var thumbUrl = (id != "-1" && pg[id].thumbnail) ? pg[id].thumbnail.source : "";
                   
                    if (id != "-1" && pg[id].langlinks && pg[id].langlinks[0]) {
                        globalEnTitle = pg[id].langlinks[0]['*'];
                    }


                     if (cached.content && cached.ts === latestTS) {
                     if (cached.content && cached.ts === latestTS && latestTS !== "0" && !cached.isEmpty) {
                         renderAll(cached.p, cached.content, cached.isTrans, cached.orig, cached.img);
                        toggleMippediaLayout(false);
                         renderAll(cached.p, cached.content, cached.isTrans, cached.orig, cached.img, cached.customUrl, cached.enTitle);
                     } else {  
                     } else {  
                         fetchFreshData(latestTS, thumbUrl);  
                         fetchPriorityData(0, latestTS, thumbUrl);  
                     }
                     }
                 },
                 },
                 error: function() { if (cached.content) renderAll(cached.p, cached.content, cached.isTrans, cached.orig, cached.img); }
                 error: function() {  
                    if (cached.content) {
                        toggleMippediaLayout(cached.isEmpty || false);
                        if (!cached.isEmpty) renderAll(cached.p, cached.content, cached.isTrans, cached.orig, cached.img, cached.customUrl, cached.enTitle);
                    }
                }
             });
             });
         }
         }


         function fetchFreshData(newTS, img) {
         function fetchPriorityData(index, newTS, img) {
             var found = false;
             if (index >= projects.length) {
             var shuffled = projects.sort(() => Math.random() - 0.5);
                localStorage.setItem(cacheKey, JSON.stringify({isEmpty: true, ts: newTS, content: ""}));
            shuffled.forEach(function(p) {
                toggleMippediaLayout(true);
                 if (found) return;
                return;
            }
 
             var p = projects[index];
 
            if (p.id === 'en') {
                if (!globalEnTitle) {
                    $.ajax({
                        url: 'https://concise.mippedia.org/api.php',
                        data: { action: 'query', prop: 'langlinks', lllang: 'en', titles: pageTitle, format: 'json', origin: '*' },
                        dataType: 'json',
                        async: false,
                        success: function(cRes) {
                            var cPg = cRes.query.pages, cId = Object.keys(cPg)[0];
                            if (cId != "-1" && cPg[cId].langlinks && cPg[cId].langlinks[0]) {
                                globalEnTitle = cPg[cId].langlinks[0]['*'];
                            }
                        }
                    });
                }
 
                if (globalEnTitle) {
                    $.ajax({
                        url: p.url,
                        data: { action: 'query', prop: 'extracts', exintro: 1, explaintext: 1, titles: globalEnTitle, format: 'json', origin: '*' },
                        dataType: 'json',
                        success: function(data) {
                            var pages = data.query.pages, pageId = Object.keys(pages)[0];
                            if (pageId != "-1" && pages[pageId].extract) {
                                var extract = cleanExtract(pages[pageId].extract);
                                finalize(p, extract, false, "", newTS, img, p.base + encodeURIComponent(globalEnTitle), globalEnTitle);
                            } else {
                                fetchPriorityData(index + 1, newTS, img);
                            }
                        },
                        error: function() { fetchPriorityData(index + 1, newTS, img); }
                    });
                 } else {
                    fetchPriorityData(index + 1, newTS, img);
                }
            } else {
                 $.ajax({
                 $.ajax({
                     url: p.url,
                     url: p.url,
Baris 97: Baris 436:
                     dataType: 'json',
                     dataType: 'json',
                     success: function(data) {
                     success: function(data) {
                        if (found) return;
                         var pages = data.query.pages, pageId = Object.keys(pages)[0];
                         var pages = data.query.pages, pageId = Object.keys(pages)[0];
                         if (pageId != "-1" && pages[pageId].extract) {
                         if (pageId != "-1" && pages[pageId].extract) {
                            found = true;
                             var extract = cleanExtract(pages[pageId].extract);
                             var extract = cleanExtract(pages[pageId].extract);
                             if (p.id === 'en') {
                             finalize(p, extract, false, "", newTS, img, p.base + encodeURIComponent(pageTitle), globalEnTitle);  
                                $.ajax({ url: "https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=id&dt=t&q=" + encodeURIComponent(extract), success: function(res) {
                        } else {
                                    var trans = ""; res[0].forEach(s => { if (s[0]) trans += s[0]; });
                            fetchPriorityData(index + 1, newTS, img);
                                    finalize(p, trans, true, extract, newTS, img);
                                }});
                            } else {  
                                finalize(p, extract, false, "", newTS, img);  
                            }
                         }
                         }
                     }
                     },
                    error: function() { fetchPriorityData(index + 1, newTS, img); }
                 });
                 });
             });
             }
         }
         }


         function finalize(p, c, t, o, ts, img) {
         function finalize(p, c, t, o, ts, img, customUrl, enTitle) {
             localStorage.setItem(cacheKey, JSON.stringify({p:p, content:c, isTrans:t, orig:o, ts:ts, img:img}));
             localStorage.setItem(cacheKey, JSON.stringify({isEmpty: false, p:p, content:c, isTrans:t, orig:o, ts:ts, img:img, customUrl: customUrl, enTitle: enTitle}));
             renderAll(p, c, t, o, img);
            toggleMippediaLayout(false);  
             renderAll(p, c, t, o, img, customUrl, enTitle);
         }
         }


         function renderAll(p, currentText, isTranslated, originalText, img) {
         function renderAll(p, currentText, isTranslated, originalText, img, customUrl, enTitle) {
             var processedText = applyAutoBold(applyDynamicContext(currentText));
             var processedText = applyAutoBold(applyDynamicContext(currentText));
           
            // Layouting Thumbnail (Tetap ringan: hanya render jika gambar ada)
             var thumbHtml = img ? '<div style="float: right; margin-left: 15px; margin-bottom: 10px; border: 1px solid #ddd; padding: 3px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"><img src="'+img+'" style="max-width: 100px; display: block; height: auto;"></div>' : '';
             var thumbHtml = img ? '<div style="float: right; margin-left: 15px; margin-bottom: 10px; border: 1px solid #ddd; padding: 3px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.1);"><img src="'+img+'" style="max-width: 100px; display: block; height: auto;"></div>' : '';
            if (!$mainContent.find('#mip-desc-section').length) {
                $mainContent.prepend($descSection);
            }


             $descSection.show();
             $descSection.show();
            // Masukkan thumbnail ke dalam konten box
             $descBox.show().html(thumbHtml + applySummary(processedText) + '<div style="clear:both;"></div>');
             $descBox.show().html(thumbHtml + applySummary(processedText) + '<div style="clear:both;"></div>');
              
              
             var footer = '<div style="font-size: 0.9em; color: #777;">Sumber Dari : <a href="' + p.base + encodeURIComponent(pageTitle) + '" target="_blank" style="color: #6a5acd; font-weight: bold; text-decoration: none;">' + p.name + '.</a>';
            var targetLink = customUrl ? customUrl : p.base + encodeURIComponent(pageTitle);
             var footer = '<div style="font-size: 0.9em; color: #777;">Sumber : <a href="' + targetLink + '" target="_blank" style="color: #6a5acd; font-weight: bold; text-decoration: none;">' + p.name + '.</a>';
             if (isTranslated) { footer += '<br><span style="font-size: 0.85em; font-style: italic;">(Diterjemahkan secara otomatis)</span> <span id="mip-toggle-orig" style="color: #6a5acd; cursor: pointer; text-decoration: underline; margin-left: 5px;">Tampilkan versi asli</span>'; }
             if (isTranslated) { footer += '<br><span style="font-size: 0.85em; font-style: italic;">(Diterjemahkan secara otomatis)</span> <span id="mip-toggle-orig" style="color: #6a5acd; cursor: pointer; text-decoration: underline; margin-left: 5px;">Tampilkan versi asli</span>'; }
             $sourceInfo.show().html(footer);
             $sourceInfo.show().html(footer);
              
              
             buildSmartLinks(p.id);
             buildSmartLinks(p.id, enTitle);
              
              
            // Handlers
             $(document).off('click', '.mip-read-btn').on('click', '.mip-read-btn', function() { $(this).hide(); $('.mip-dots').hide(); $('.mip-more').fadeIn(); });
             $(document).off('click', '.mip-read-btn').on('click', '.mip-read-btn', function() { $(this).hide(); $('.mip-dots').hide(); $('.mip-more').fadeIn(); });
             $(document).off('click', '#mip-toggle-orig').on('click', '#mip-toggle-orig', function() {
             $(document).off('click', '#mip-toggle-orig').on('click', '#mip-toggle-orig', function() {
                 var isOrig = ($(this).text() === 'Tampilkan versi asli');
                 var isOrig = ($(this).text() === 'Tampilkan versi asli');
                 var textToDisplay = applyAutoBold(applyDynamicContext(isOrig ? originalText : currentText));
                 var textToDisplay = applyAutoBold(applyDynamicContext(isOrig ? originalText : currentText));
                // Pastikan thumbnail tetap ada saat toggle versi asli
                 $descBox.html(thumbHtml + applySummary(textToDisplay) + '<div style="clear:both;"></div>');
                 $descBox.html(thumbHtml + applySummary(textToDisplay) + '<div style="clear:both;"></div>');
                 $(this).text(isOrig ? 'Tampilkan terjemahan' : 'Tampilkan versi asli');
                 $(this).text(isOrig ? 'Tampilkan terjemahan' : 'Tampilkan versi asli');
Baris 531: Baris 865:


/* ==========================================================
/* ==========================================================
   🌐 MIPPEDIA SHORTDESC - DATA CENTER
   🌐 MIPPEDIA SHORTDESC - DATA CENTER (PREMIUM UI)
   Status: INDEPENDENT SYSTEM (Separated from Sitelinks)
   Status: INDEPENDENT & REFINED VISUAL
   Storage: MediaWiki:ShortDesc-Data.json
   Storage: MediaWiki:ShortDesc-Data.json
   ========================================================== */
   ========================================================== */
Baris 541: Baris 875:


         var pageTitle = mw.config.get('wgPageName');
         var pageTitle = mw.config.get('wgPageName');
         var storagePage = 'MediaWiki:ShortDesc-Data.json'; // File penyimpanan berbeda
         var storagePage = 'MediaWiki:ShortDesc-Data.json';
         var isAdmin = mw.config.get('wgUserGroups').some(g => ['sysop', 'interface-admin'].includes(g));
         var isAdmin = mw.config.get('wgUserGroups').some(g => ['sysop', 'interface-admin'].includes(g));


         // UI Box untuk Input Deskripsi
        var langs = {
            id: { label: 'Mippedia bahasa Indonesia', short: 'ID', color: '#6366f1' },
            en: { label: 'Mippedia bahasa Inggris', short: 'EN', color: '#3b82f6' },
            concise: { label: 'Mippedia bahasa Indonesia ringkas', short: 'CON', color: '#14b8a6' }
        };
 
         // UI Box dengan Spasi dan Desain Baru
         var html = `
         var html = `
             <div id="desc-manager" style="border: 2px solid #20b2aa; padding: 15px; background: #fff; margin-bottom: 20px; border-radius: 10px; font-family: sans-serif;">
             <div id="desc-manager" style="background:#ffffff; border:1px solid #f1f5f9; border-left:4px solid #6366f1; border-radius:12px; padding:20px; margin: 20px 0 25px 0; box-shadow:0 4px 12px rgba(0,0,0,0.03); font-family: -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
                 <div style="font-weight: bold; color: #20b2aa; margin-bottom: 10px; display: flex; justify-content: space-between; align-items: center;">
                 <div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:12px;">
                     <span>📝 Mippedia Short Description</span>
                    <span style="font-size:13px; font-weight:700; color:#475569; text-transform:uppercase; letter-spacing:0.5px; display:flex; align-items:center; gap:6px;">
                     <button id="desc-toggle" style="cursor:pointer; padding:2px 8px; font-size:11px;">Buka Editor</button>
                        Label Deskripsi
                     </span>
                     <span id="desc-open-editor" style="cursor:pointer; padding:5px; transition:0.2s;" title="Edit Deskripsi">
                        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#94a3b8" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"></path></svg>
                    </span>
                 </div>
                 </div>
                <div id="desc-current" style="font-size: 13px; color: #666; font-style: italic;">Memuat data deskripsi...</div>
                  
                  
                 <div id="desc-editor" style="display:none; margin-top: 10px; border-top: 1px solid #eee; padding-top: 10px;">
                 <div id="desc-status-display" style="display:flex; flex-direction:column; gap:8px;">
                     <div style="margin-bottom:8px;">
                     <small style="color:#94a3b8; font-style:italic;">Sinkronisasi data...</small>
                        <label style="display:block; font-size:11px; font-weight:bold;">ID (Indonesia):</label>
                </div>
                        <input type="text" id="desc-in-id" style="width:100%; padding:6px; border:1px solid #ccc; border-radius:4px;">
 
                     </div>
                <div id="desc-editor" style="display:none; margin-top:18px; border-top:1px dashed #e2e8f0; padding-top:18px;">
                    <div style="margin-bottom:8px;">
                     ${Object.keys(langs).map(k => `
                        <label style="display:block; font-size:11px; font-weight:bold;">EN (English):</label>
                        <div style="margin-bottom:12px;">
                        <input type="text" id="desc-in-en" style="width:100%; padding:6px; border:1px solid #ccc; border-radius:4px;">
                            <label style="display:block; font-size:11px; font-weight:bold; color:#64748b; margin-bottom:4px;">${langs[k].label}</label>
                    </div>
                            <input type="text" id="desc-in-${k}" placeholder="Tulis deskripsi..." style="width:100%; padding:10px; font-size:13px; border:1px solid #e2e8f0; border-radius:8px; box-sizing:border-box; outline:none; focus:border-color:#6366f1;">
                    <div style="margin-bottom:12px;">
                        </div>
                        <label style="display:block; font-size:11px; font-weight:bold;">CONCISE (Ringkas):</label>
                    `).join('')}
                        <input type="text" id="desc-in-concise" style="width:100%; padding:6px; border:1px solid #ccc; border-radius:4px;">
                     <button id="desc-save" style="width:100%; padding:12px; background:#6366f1; color:white; border:none; border-radius:8px; font-size:13px; font-weight:600; cursor:pointer; margin-top:5px;">Terbitkan</button>
                    </div>
                     <button id="desc-save" style="width:100%; background:#20b2aa; color:white; border:none; padding:8px; border-radius:5px; font-weight:bold; cursor:pointer;">Simpan Deskripsi</button>
                 </div>
                 </div>
             </div>`;
             </div>`;
Baris 572: Baris 913:
         $('.mw-parser-output').prepend(html);
         $('.mw-parser-output').prepend(html);


         // 1. Load data dari JSON
         function loadDescStatus() {
        new mw.Api().get({ action: 'query', prop: 'revisions', titles: storagePage, rvprop: 'content', formatversion: 2 }).done(function(res) {
            new mw.Api().get({ action: 'query', prop: 'revisions', titles: storagePage, rvprop: 'content', formatversion: 2 }).done(function(res) {
            var content = res.query.pages[0].revisions ? res.query.pages[0].revisions[0].content : "{}";
                var content = res.query.pages[0].revisions ? res.query.pages[0].revisions[0].content : "{}";
            var fullData = JSON.parse(content);
                var fullData = JSON.parse(content);
            var myData = fullData[pageTitle] || {};
                var myData = fullData[pageTitle] || {};
                var $statusBox = $('#desc-status-display').empty();
                var foundAny = false;
 
                Object.keys(langs).forEach(k => {
                    var val = myData[k];
                    if (val && val.trim() !== "") {
                        foundAny = true;
                        $statusBox.append(`
                            <div style="display:flex; align-items:center; gap:10px;">
                                <span style="background:${langs[k].color}15; color:${langs[k].color}; padding:3px 10px; border-radius:20px; font-size:10px; font-weight:800; border:1px solid ${langs[k].color}30; min-width:35px; text-align:center;">${langs[k].short}</span>
                                <span style="font-size:13px; color:#334155; line-height:1.4;">${val}</span>
                            </div>
                        `);
                        $('#desc-in-' + k).val(val);
                    }
                });


            $('#desc-in-id').val(myData.id || '');
                if (!foundAny) {
            $('#desc-in-en').val(myData.en || '');
                    $statusBox.html('<div style="color:#cbd5e0; font-size:13px; background:#f8fafc; padding:10px; border-radius:8px; border:1px dashed #e2e8f0;">Belum ada metadata deskripsi yang dikaitkan dengan entitas ini.</div>');
            $('#desc-in-concise').val(myData.concise || '');
                }
           
             });
             $('#desc-current').html(`<b>Current:</b> ${myData.id || 'Belum ada deskripsi'}`);
         }
         });


         $('#desc-toggle').click(function() { $('#desc-editor').slideToggle(); });
         $(document).on('click', '#desc-open-editor', function() { $('#desc-editor').slideToggle(); });


         // 2. Simpan data ke JSON
         $(document).on('click', '#desc-save', function() {
        $('#desc-save').click(function() {
             if (!isAdmin) { alert('Akses ditolak. Anda memerlukan izin pengurus.'); return; }
             if (!isAdmin) { alert('Hanya Pengurus yang bisa menyimpan data.'); return; }
             var $btn = $(this).text('Proses Sinkronisasi...').css('opacity','0.7');
              
             var api = new mw.Api();
             var api = new mw.Api();
             var newData = {
             var newData = { id: $('#desc-in-id').val().trim(), en: $('#desc-in-en').val().trim(), concise: $('#desc-in-concise').val().trim() };
                id: $('#desc-in-id').val().trim(),
                en: $('#desc-in-en').val().trim(),
                concise: $('#desc-in-concise').val().trim()
            };


             api.get({ action: 'query', prop: 'revisions', titles: storagePage, rvprop: 'content', formatversion: 2 }).done(function(r) {
             api.get({ action: 'query', prop: 'revisions', titles: storagePage, rvprop: 'content', formatversion: 2 }).done(function(r) {
                 var json = r.query.pages[0].revisions ? JSON.parse(r.query.pages[0].revisions[0].content) : {};
                 var json = r.query.pages[0].revisions ? JSON.parse(r.query.pages[0].revisions[0].content) : {};
                 json[pageTitle] = newData;
                 if (!newData.id && !newData.en && !newData.concise) { delete json[pageTitle]; } else { json[pageTitle] = newData; }


                 api.postWithEditToken({
                 api.postWithEditToken({
Baris 606: Baris 957:
                     title: storagePage,
                     title: storagePage,
                     text: JSON.stringify(json, null, 2),
                     text: JSON.stringify(json, null, 2),
                     summary: 'Update ShortDesc: ' + pageTitle
                     summary: 'Update Entity Description: ' + pageTitle
                 }).done(function() {
                 }).done(function() { location.reload(); });
                    alert('Berhasil disimpan!');
                    location.reload();
                });
             });
             });
         });
         });
        loadDescStatus();
     });
     });
})(jQuery, mediaWiki);
})(jQuery, mediaWiki);
/* ==========================================================
  📍 MIPPEDIA DATA - ULTIMATE TEXT-TO-MAP LOCATOR
  Fitur: Deteksi lokasi dari teks dengan database label lengkap
  ========================================================== */
(function() {
    $(document).ready(function() {
        var locationText = "";
       
        // --- DAFTAR LABEL SUPER LENGKAP & MASUK AKAL ---
        var locationLabels = [
            // Umum & Geografis
            'lokasi', 'tempat', 'wilayah', 'daerah', 'koordinat', 'alamat',
            // Orang (Biografi)
            'tempat lahir', 'asal', 'kediaman', 'domisili', 'negara asal',
            // Organisasi & Perusahaan
            'kantor pusat', 'markas', 'cabang utama', 'titik operasi',
            // Makanan & Budaya
            'asal wilayah', 'daerah asal', 'negara sumber', 'kawasan',
            // Sejarah & Bangunan
            'letak', 'posisi', 'titik temu', 'orbit'
        ];
        // Jalankan pencarian di tabel data (infobox atau wikitable)
        $('.wikitable tr, .infobox tr, .mip-data-table tr').each(function() {
            var label = $(this).find('th').text().toLowerCase().trim();
            // Hilangkan karakter spesial kayak titik dua biar matching-nya akurat
            label = label.replace(/[:]/g, '');
            var isMatch = locationLabels.some(function(target) {
                return label === target || label.includes(target);
            });
            if (isMatch) {
                var rawValue = $(this).find('td').text().trim();
                // Ambil bagian utama sebelum tanda koma, kurung, atau titik koma
                // Misal: "Bandung, Jawa Barat" -> ambil "Bandung"
                locationText = rawValue.split(/[,(\[;]/)[0].trim();
               
                if (locationText.length > 2) return false; // Stop jika sudah ketemu yang valid
            }
        });
        // Eksekusi Render Peta
        if (locationText && locationText.length > 2) {
            var $mapWrapper = $('<div>').css({
                'margin': '15px 0',
                'padding': '12px',
                'border': '1px solid #ddd',
                'background': '#ffffff',
                'border-radius': '10px',
                'box-shadow': '0 4px 12px rgba(0,0,0,0.08)'
            });
            $mapWrapper.append('<div style="font-weight:bold; font-size:0.9em; margin-bottom:10px; color:#444; display:flex; align-items:center;">' +
                              '<span style="margin-right:8px;">🗺️</span>Pratinjau Lokasi : ' + locationText + '</div>');
            // Embed URL dengan pencarian teks (Nominatim)
            var embedUrl = "https://www.openstreetmap.org/export/embed.html?layer=mapnik&q=" + encodeURIComponent(locationText);
            var $iframe = $('<iframe>').attr({
                'src': embedUrl,
                'width': '100%',
                'height': '280',
                'frameborder': '0',
                'style': 'border-radius: 8px; border: 1px solid #eee;'
            });
            $mapWrapper.append($iframe);
           
            // Link eksternal yang lebih rapi
            var gMapsUrl = "https://www.google.com/maps/search/" + encodeURIComponent(locationText);
            $mapWrapper.append('<div style="text-align:right; margin-top:8px;">' +
                              '<a href="' + gMapsUrl + '" target="_blank" style="font-size:0.75em; color:#6a5acd; font-weight:bold; text-decoration:none; text-transform:uppercase; letter-spacing:0.5px;">Buka di Google Maps →</a></div>');
            // Masukkan ke bawah section deskripsi otomatis
            if ($('#mip-desc-section').length) {
                $('#mip-desc-section').after($mapWrapper);
            }
        }
    });
})();