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
Baris 1: Baris 1:
/**
/**
  * ====================================================================
  * ====================================================================
  * MIPPEDIA DATA - ADVANCED STRUCTURED DATA SYSTEM (V9.0 SECURE ULTIMATE)
  * MIPPEDIA DATA - ADVANCED STRUCTURED DATA SYSTEM (V8.7 SECURE ULTIMATE)
  * Fitur Baru: Top Social Media Container, Smart Image/Media Preview,  
  * Fitur: ALL ORIGINAL V8.6 FEATURES + Social Header Separation,
  * Wiki Page Autocomplete (OpenSearch), Auto-Wikilink Detection.
* Smart Media URL Preview (YouTube/Spotify/Images), Wiki Autocomplete,
  * Ultra-Strict OWASP XSS Security, Mobile Optimized.
  * ====================================================================
  * ====================================================================
  */
  */
Baris 76: Baris 77:
         var svgVerified = '<svg width="15" height="15" viewBox="0 0 24 24" fill="#3b82f6" stroke="#ffffff" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>';
         var svgVerified = '<svg width="15" height="15" viewBox="0 0 24 24" fill="#3b82f6" stroke="#ffffff" stroke-width="2"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"></path><polyline points="22 4 12 14.01 9 11.01"></polyline></svg>';
         var svgHelp = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>';
         var svgHelp = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"></path><line x1="12" y1="17" x2="12.01" y2="17"></line></svg>';
        var svgMedia = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><circle cx="8.5" cy="8.5" r="1.5"></circle><polyline points="21 15 16 10 5 21"></polyline></svg>';


         // 4. HELPER UTILITY & ADVANCED SECURITY (Tingkat Tinggi OWASP)
         // 4. HELPER UTILITY & ADVANCED SECURITY (Tingkat Tinggi OWASP)
Baris 128: Baris 128:
         }
         }


         // 5. STYLESHEET (V9.0 dengan Komponen Baru)
         // 5. STYLESHEET  
         var css = `
         var css = `
             #mippedia-data-app {
             #mippedia-data-app {
Baris 152: Baris 152:
             .md-search-input:focus { background: #fff; border-color: #4f46e5; outline: none; }
             .md-search-input:focus { background: #fff; border-color: #4f46e5; outline: none; }
             .md-search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: #94a3b8; }
             .md-search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: #94a3b8; }
            /* NEW: Container Media Sosial Terpisah di Atas */
            .md-top-social-container { background: #f8fafc; border: 1px solid #e2e8f0; padding: 14px 16px; border-radius: 12px; margin-bottom: 16px; box-shadow: inset 0 2px 4px rgba(0,0,0,0.02); }
            .md-top-social-label { font-size: 0.72rem; font-weight: 800; color: #64748b; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.6px; display: flex; align-items: center; gap: 5px; }
            .md-social-group { display: flex; flex-wrap: wrap; gap: 8px; }
            .md-social-badge { display: inline-flex; align-items: center; gap: 6px; padding: 6px 12px; border-radius: 20px; font-size: 0.8rem; font-weight: 600; color: white !important; text-decoration: none; transition: transform 0.2s, opacity 0.2s; box-shadow: 0 2px 4px rgba(0,0,0,0.1); }
            .md-social-badge:hover { transform: translateY(-2px); opacity: 0.92; text-decoration: none; }


             .md-row-display { display: flex; justify-content: space-between; align-items: flex-start; padding: 16px 12px; border-bottom: 1px solid #f1f5f9; transition: all 0.2s; border-radius: 12px; gap: 12px; margin-bottom: 8px; background: #fff; }
             .md-row-display { display: flex; justify-content: space-between; align-items: flex-start; padding: 16px 12px; border-bottom: 1px solid #f1f5f9; transition: all 0.2s; border-radius: 12px; gap: 12px; margin-bottom: 8px; background: #fff; }
Baris 166: Baris 159:
             .md-data-label { font-size: 0.72rem; color: #64748b; font-weight: 800; text-transform: uppercase; letter-spacing: 0.6px; }
             .md-data-label { font-size: 0.72rem; color: #64748b; font-weight: 800; text-transform: uppercase; letter-spacing: 0.6px; }
             .md-data-value-wrap { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
             .md-data-value-wrap { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
             .md-data-value { font-size: 1.02rem; color: #0f172a; line-height: 1.4; font-weight: 600; word-break: break-word; }
             .md-data-value { font-size: 1.02rem; color: #0f172a; line-height: 1.4; font-weight: 600; word-break: break-word; width: 100%; }
           
            /* NEW: Image/Media Preview Styles */
            .md-media-preview-container { margin-top: 6px; width: 100%; display: block; }
            .md-media-preview-img { max-width: 100%; max-height: 250px; border-radius: 8px; border: 1px solid #cbd5e1; background: #f1f5f9; object-fit: contain; box-shadow: 0 2px 8px rgba(0,0,0,0.05); }
              
              
            /* NEW: Autocomplete Styles */
            .md-autocomplete-wrapper { position: relative; width: 100%; }
            .md-autocomplete-list { position: absolute; top: calc(100% + 4px); left: 0; right: 0; background: #ffffff; border: 1px solid #cbd5e1; border-radius: 8px; z-index: 1000; max-height: 200px; overflow-y: auto; box-shadow: 0 10px 25px rgba(0,0,0,0.15); display: none; }
            .md-autocomplete-item { padding: 10px 14px; cursor: pointer; font-size: 0.9rem; border-bottom: 1px solid #f1f5f9; color: #0f172a; transition: 0.1s; display: flex; align-items: center; gap: 8px; }
            .md-autocomplete-item:last-child { border-bottom: none; }
            .md-autocomplete-item:hover { background: #e0e7ff; color: #4f46e5; font-weight: 600; padding-left: 18px; }
             .md-btn-copy { background: none; border: none; color: #94a3b8; cursor: pointer; padding: 2px 4px; border-radius: 4px; transition: 0.2s; display: inline-flex; align-items: center; }
             .md-btn-copy { background: none; border: none; color: #94a3b8; cursor: pointer; padding: 2px 4px; border-radius: 4px; transition: 0.2s; display: inline-flex; align-items: center; }
             .md-btn-copy:hover { color: #4f46e5; background: #e0e7ff; }
             .md-btn-copy:hover { color: #4f46e5; background: #e0e7ff; }
            .md-social-group { display: flex; flex-wrap: wrap; gap: 6px; }
            .md-social-badge { display: inline-flex; align-items: center; gap: 6px; padding: 5px 11px; border-radius: 18px; font-size: 0.78rem; font-weight: 600; color: white; text-decoration: none; transition: transform 0.2s, opacity 0.2s; box-shadow: 0 2px 4px rgba(0,0,0,0.05); }
            .md-social-badge:hover { transform: translateY(-2px); opacity: 0.92; color: white; text-decoration: none; }


             .md-ref-container { margin-top: 10px; width: 100%; box-sizing: border-box; }
             .md-ref-container { margin-top: 10px; width: 100%; box-sizing: border-box; }
Baris 200: Baris 186:
             .md-form-box { background: #ffffff; border-radius: 12px; padding: 18px; margin-top: 16px; border: 1px solid #cbd5e1; box-shadow: 0 4px 16px rgba(0,0,0,0.05); }
             .md-form-box { background: #ffffff; border-radius: 12px; padding: 18px; margin-top: 16px; border: 1px solid #cbd5e1; box-shadow: 0 4px 16px rgba(0,0,0,0.05); }
             .md-form-title { font-size: 1.05rem; font-weight: 800; color: #0f172a; margin: 0 0 16px 0; border-bottom: 2px solid #e2e8f0; padding-bottom: 8px; }
             .md-form-title { font-size: 1.05rem; font-weight: 800; color: #0f172a; margin: 0 0 16px 0; border-bottom: 2px solid #e2e8f0; padding-bottom: 8px; }
             .md-form-group { margin-bottom: 14px; }
             .md-form-group { margin-bottom: 14px; position: relative; }
             .md-form-group label { display: flex; align-items:center; font-size: 0.82rem; font-weight: 700; margin-bottom: 6px; color: #334155; }
             .md-form-group label { display: flex; align-items:center; font-size: 0.82rem; font-weight: 700; margin-bottom: 6px; color: #334155; }
             .md-input, .md-select, .md-textarea { width: 100%; padding: 9px 12px; border: 1px solid #cbd5e1; border-radius: 8px; box-sizing: border-box; font-family: inherit; font-size: 0.9rem; transition: 0.2s; background: #fff; }
             .md-input, .md-select, .md-textarea { width: 100%; padding: 9px 12px; border: 1px solid #cbd5e1; border-radius: 8px; box-sizing: border-box; font-family: inherit; font-size: 0.9rem; transition: 0.2s; background: #fff; }
             .md-input:focus, .md-select:focus, .md-textarea:focus { border-color: #4f46e5; outline: none; box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.15); }
             .md-input:focus, .md-select:focus, .md-textarea:focus { border-color: #4f46e5; outline: none; box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.15); }
              
              
            .md-autocomplete-list { position: absolute; top: 100%; left: 0; right: 0; z-index: 100; background: #fff; border: 1px solid #cbd5e1; border-radius: 8px; max-height: 200px; overflow-y: auto; box-shadow: 0 4px 12px rgba(0,0,0,0.1); margin-top: 4px; display: none; }
            .md-autocomplete-item { padding: 8px 12px; cursor: pointer; border-bottom: 1px solid #f1f5f9; font-size: 0.85rem; color: #0f172a; transition: background 0.2s; }
            .md-autocomplete-item:hover { background: #f8fafc; color: #4f46e5; font-weight: 600; }
             .md-ref-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; background: #f8fafc; padding: 12px; border-radius: 8px; border: 1px dashed #cbd5e1; }
             .md-ref-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; background: #f8fafc; padding: 12px; border-radius: 8px; border: 1px dashed #cbd5e1; }
             .md-social-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; background: #f1f5f9; padding: 12px; border-radius: 8px; border: 1px solid #cbd5e1; max-height: 280px; overflow-y: auto; }
             .md-social-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; background: #f1f5f9; padding: 12px; border-radius: 8px; border: 1px solid #cbd5e1; max-height: 280px; overflow-y: auto; }
Baris 275: Baris 265:
                 var leftPos = offset.left - 15;
                 var leftPos = offset.left - 15;
                  
                  
                 if (leftPos + ttWidth > winWidth - 15) leftPos = winWidth - ttWidth - 15;
                 if (leftPos + ttWidth > winWidth - 15) { leftPos = winWidth - ttWidth - 15; }
                 if (leftPos < 10) leftPos = 10;
                 if (leftPos < 10) { leftPos = 10; }
                  
                  
                 $tt.css({ top: offset.top - $tt.outerHeight() - 10, left: leftPos }).fadeIn(200);
                 $tt.css({ top: offset.top - $tt.outerHeight() - 10, left: leftPos }).fadeIn(200);
Baris 303: Baris 293:
         function buildWikitextVal(item) {
         function buildWikitextVal(item) {
             var val = item.val;
             var val = item.val;
            if (item.isMedia) return val; // Raw output agar wikitext tidak rusak oleh iframe
              
              
            // Fix: Kalau item itu Pratinjau Gambar/Media, output URL standar tanpa kurung siku ganda.
            if (item.isMediaPreview && val.match(/^https?:\/\//i)) {
                return "[" + val + " Tautan Media]";
            }
             if (item.linkTarget && item.linkTarget.trim() !== '') {
             if (item.linkTarget && item.linkTarget.trim() !== '') {
                 return "[[" + item.linkTarget.trim() + "|" + val + "]]";
                 return "[[" + item.linkTarget.trim() + "|" + val + "]]";
Baris 344: Baris 331:
             if ($btnEl) $btnEl.addClass('is-loading').prop('disabled', true);
             if ($btnEl) $btnEl.addClass('is-loading').prop('disabled', true);
              
              
             var summaryTxt = customSummary ? ('Mippedia Data: ' + customSummary) : ('Mippedia Data V9.0: ' + actionType);
             var summaryTxt = customSummary ? ('Mippedia Data: ' + customSummary) : ('Mippedia Data V8.7: ' + actionType);
             var jsonStr = JSON.stringify({ locked: isLocked, items: dataItems });
             var jsonStr = JSON.stringify({ locked: isLocked, items: dataItems });
             var wt = "<!-- MIPPEDIA_DATA_START | " + jsonStr + " -->\n";
             var wt = "<!-- MIPPEDIA_DATA_START | " + jsonStr + " -->\n";
Baris 406: Baris 393:
             $app.append($header);
             $app.append($header);


            // Tampilkan Search Box
             if (dataItems.length > 2) {
             if (dataItems.length > 2) {
                 var $searchBox = $('<div class="md-search-box"><span class="md-search-icon">' + svgSearch + '</span><input type="text" class="md-search-input" placeholder="Cari data terstruktur..."></div>');
                 var $searchBox = $('<div class="md-search-box"><span class="md-search-icon">' + svgSearch + '</span><input type="text" class="md-search-input" placeholder="Cari data terstruktur..."></div>');
Baris 419: Baris 405:
             }
             }


             // --- FITUR BARU: Pisahkan Sosial Media ke Kotak Atas ---
             // Ekstrak dan Tampilkan Kotak Media Sosial Terpisah (Di Atas)
             var aggregatedSocials = {};
             var socialDataObj = null;
            var hasSocials = false;
             dataItems.forEach(function(it) {
             dataItems.forEach(function(item) {
                 if (it.isPersonOrg && it.socials && Object.keys(it.socials).length > 0) { socialDataObj = it.socials; }
                 if (item.isPersonOrg && item.socials) {
                    Object.assign(aggregatedSocials, item.socials);
                    hasSocials = true;
                }
             });
             });


             if (hasSocials) {
             if (socialDataObj) {
                 var $topSocialContainer = $('<div class="md-top-social-container"></div>');
                 var $socialHeaderBox = $('<div style="background: linear-gradient(135deg, #f8fafc, #f1f5f9); padding: 15px; border-radius: 12px; border: 1px solid #e2e8f0; margin-bottom: 20px; display: flex; flex-direction: column; gap: 10px; align-items: center; box-shadow: 0 2px 10px rgba(0,0,0,0.02);"></div>');
                 $topSocialContainer.append('<div class="md-top-social-label">' + svgVerified + ' PROFIL MEDIA SOSIAL & MUSIK</div>');
                 $socialHeaderBox.append('<span style="font-size: 0.8rem; font-weight: 800; color: #64748b; text-transform: uppercase; letter-spacing: 1px;">Profil Media Sosial & Musik Resmi</span>');
                 var $socialGroup = $('<div class="md-social-group"></div>');
                 var $socialGroupGlobal = $('<div class="md-social-group" style="justify-content: center;"></div>');
               
                 Object.keys(SOCIAL_PLATFORMS).forEach(function(key) {
                 Object.keys(SOCIAL_PLATFORMS).forEach(function(key) {
                     if (aggregatedSocials[key]) {
                     var uname = socialDataObj[key];
                    if (uname) {
                         var platform = SOCIAL_PLATFORMS[key];
                         var platform = SOCIAL_PLATFORMS[key];
                         var fullUrl = platform.base + aggregatedSocials[key].replace(/^@/, '');
                         var fullUrl = platform.base + uname.replace(/^@/, '');
                         var $badge = $('<a class="md-social-badge" href="' + sanitizeHTML(fullUrl) + '" target="_blank" rel="noopener noreferrer" style="background:' + platform.color + ';">' + platform.icon + ' ' + sanitizeHTML(platform.name) + '</a>');
                         var $badge = $('<a class="md-social-badge" href="' + sanitizeHTML(fullUrl) + '" target="_blank" rel="noopener noreferrer" style="background:' + platform.color + ';">' + platform.icon + ' ' + sanitizeHTML(platform.name) + '</a>');
                         $socialGroup.append($badge);
                         $socialGroupGlobal.append($badge);
                     }
                     }
                 });
                 });
                 $topSocialContainer.append($socialGroup);
                 $socialHeaderBox.append($socialGroupGlobal);
                 $app.append($topSocialContainer);
                 $app.append($socialHeaderBox);
             }
             }
            // -----------------------------------------------------


             var $list = $('<div class="md-list"></div>');
             var $list = $('<div class="md-list"></div>');
Baris 458: Baris 441:
                     var rawVal = item.val || '';
                     var rawVal = item.val || '';


                     // Jika ini Pratinjau Gambar/Media, maka deteksi URL nya dan masukkan ke tag img
                     // Deteksi Pratinjau Media Cerdas
                     if (item.isMediaPreview && rawVal.match(/^https?:\/\//i)) {
                     if (item.isMedia && rawVal.match(/^https?:\/\//i)) {
                         displayValHtml = '<a href="' + sanitizeHTML(rawVal) + '" target="_blank" style="color:#2563eb; text-decoration:none; font-weight:700; word-break:break-all;">' + extractDomain(rawVal) + '</a>';
                         if (rawVal.match(/\.(jpeg|jpg|gif|png|webp|bmp)$/i)) {
                        // Render Gambar Aman (OWASP xss escape via sanitizeHTML dan single quotes aman di atribut onerror)
                            displayValHtml = '<img src="' + sanitizeHTML(rawVal) + '" style="max-width:100%; border-radius:8px; margin-top:6px; box-shadow:0 2px 8px rgba(0,0,0,0.1);">';
                        displayValHtml += '<div class="md-media-preview-container"><a href="' + sanitizeHTML(rawVal) + '" target="_blank"><img src="' + sanitizeHTML(rawVal) + '" class="md-media-preview-img" alt="Pratinjau Media" onerror="this.outerHTML=\'<span style=\\\'color:#dc2626; font-size:0.75rem; font-weight:600; display:block; margin-top:4px;\\\'>[Gagal memuat pratinjau. Pastikan URL berupa gambar langsung]</span>\'"></a></div>';
                        } else if (rawVal.match(/(youtube\.com|youtu\.be)/i)) {
                    }
                            var ytId = rawVal.match(/(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))([\w-]{11})/);
                    else if (rawVal.match(/^https?:\/\//i)) {
                            if(ytId) {
                        displayValHtml = '<a href="' + sanitizeHTML(rawVal) + '" target="_blank" rel="noopener noreferrer" style="color:#2563eb; font-weight:700; text-decoration:none;">' + extractDomain(rawVal) + '</a>';
                                displayValHtml = '<div style="position:relative; width:100%; padding-bottom:56.25%; height:0; border-radius:8px; overflow:hidden; margin-top:6px;"><iframe src="https://www.youtube.com/embed/' + ytId[1] + '" style="position:absolute; top:0; left:0; width:100%; height:100%; border:0;" allowfullscreen></iframe></div>';
                    } else if (item.linkTarget && item.linkTarget.trim() !== '') {
                            } else { displayValHtml = '<a href="' + sanitizeHTML(rawVal) + '" target="_blank">' + sanitizeHTML(rawVal) + '</a>'; }
                        displayValHtml = '<a href="' + mw.util.getUrl(item.linkTarget.trim()) + '" style="color:#2563eb; font-weight:700; text-decoration:none;">' + sanitizeHTML(rawVal) + '</a>';
                        } else if (rawVal.match(/spotify\.com/i)) {
                    } else if (item.linkSplit) {
                             var spotUrl = rawVal.replace('spotify.com/', 'spotify.com/embed/');
                        var parts = rawVal.split(/([,/\s]+dan\s+|[,/\s]+atau\s+|[,/]+)/gi);
                            displayValHtml = '<iframe src="' + sanitizeHTML(spotUrl) + '" width="100%" height="152" frameborder="0" allowtransparency="true" allow="encrypted-media" style="border-radius:8px; margin-top:6px;"></iframe>';
                        displayValHtml = parts.map(function(p) {
                         } else {
                             var trimmed = p.trim();
                            displayValHtml = '<a href="' + sanitizeHTML(rawVal) + '" target="_blank" class="md-btn-visit" style="margin-top:6px;">Buka URL Pratinjau</a>';
                            if (trimmed && !p.match(/^[,/\s]+$/) && trimmed.toLowerCase() !== 'dan' && trimmed.toLowerCase() !== 'atau') {
                        }
                                return '<a href="' + mw.util.getUrl(trimmed) + '" style="color:#2563eb; font-weight:700; text-decoration:none;">' + sanitizeHTML(trimmed) + '</a>';
                            }
                            return sanitizeHTML(p);
                         }).join('');
                    } else if (item.link) {
                        displayValHtml = '<a href="' + mw.util.getUrl(rawVal) + '" style="color:#2563eb; font-weight:700; text-decoration:none;">' + sanitizeHTML(rawVal) + '</a>';
                     } else {
                     } else {
                         displayValHtml = sanitizeHTML(rawVal);
                         // Sistem Link Biasa
                        if (rawVal.match(/^https?:\/\//i)) {
                            displayValHtml = '<a href="' + sanitizeHTML(rawVal) + '" target="_blank" rel="noopener noreferrer" style="color:#2563eb; font-weight:700; text-decoration:none;">' + extractDomain(rawVal) + '</a>';
                        } else if (item.linkTarget && item.linkTarget.trim() !== '') {
                            displayValHtml = '<a href="' + mw.util.getUrl(item.linkTarget.trim()) + '" style="color:#2563eb; font-weight:700; text-decoration:none;">' + sanitizeHTML(rawVal) + '</a>';
                        } else if (item.linkSplit) {
                            var parts = rawVal.split(/([,/\s]+dan\s+|[,/\s]+atau\s+|[,/]+)/gi);
                            displayValHtml = parts.map(function(p) {
                                var trimmed = p.trim();
                                if (trimmed && !p.match(/^[,/\s]+$/) && trimmed.toLowerCase() !== 'dan' && trimmed.toLowerCase() !== 'atau') {
                                    return '<a href="' + mw.util.getUrl(trimmed) + '" style="color:#2563eb; font-weight:700; text-decoration:none;">' + sanitizeHTML(trimmed) + '</a>';
                                }
                                return sanitizeHTML(p);
                            }).join('');
                        } else if (item.link) {
                            displayValHtml = '<a href="' + mw.util.getUrl(rawVal) + '" style="color:#2563eb; font-weight:700; text-decoration:none;">' + sanitizeHTML(rawVal) + '</a>';
                        } else {
                            displayValHtml = sanitizeHTML(rawVal);
                        }
                     }
                     }


                     var $valWrap = $('<div class="md-data-value-wrap"></div>');
                     var $valWrap = $('<div class="md-data-value-wrap"></div>');
                     $valWrap.append('<span class="md-data-value">' + displayValHtml + '</span>');
                     $valWrap.append('<div class="md-data-value">' + displayValHtml + '</div>');
                      
                      
                     var $copyBtn = $('<button class="md-btn-copy" title="Salin teks">' + svgCopy + '</button>');
                     if (!item.isMedia) {
                    $copyBtn.on('click', function() {
                        var $copyBtn = $('<button class="md-btn-copy" title="Salin teks">' + svgCopy + '</button>');
                        navigator.clipboard.writeText(rawVal);
                        $copyBtn.on('click', function() {
                        $(this).css('color', '#16a34a');
                            navigator.clipboard.writeText(rawVal);
                        setTimeout(function(){ $copyBtn.css('color', '#94a3b8'); }, 1200);
                            $(this).css('color', '#16a34a');
                    });
                            setTimeout(function(){ $copyBtn.css('color', '#94a3b8'); }, 1200);
                    $valWrap.append($copyBtn);
                        });
                        $valWrap.append($copyBtn);
                    }
                     $info.append($valWrap);
                     $info.append($valWrap);
                      
                      
Baris 525: Baris 522:
                         $refDetails.find('.md-verified-badge').on('click', function(e) {
                         $refDetails.find('.md-verified-badge').on('click', function(e) {
                             e.preventDefault(); e.stopPropagation();
                             e.preventDefault(); e.stopPropagation();
                             showModal('Situs Terverifikasi', 'Centang biru menandakan bahwa URL referensi ini valid dan berasal dari entitas, media publikasi besar, portal populer, atau pangkalan data resmi yang bereputasi kredibel.');
                             showModal('Situs Terverifikasi', 'Centang biru menandakan bahwa URL referensi ini valid dan berasal dari entitas, media publikasi besar, portal populer, atau pangkalan data resmi (seperti Kompas, YouTube, Apple Music, Spotify, dll) yang bereputasi kredibel.');
                         });
                         });


Baris 564: Baris 561:
              
              
             var draftData = localStorage.getItem(DRAFT_KEY);
             var draftData = localStorage.getItem(DRAFT_KEY);
             var currentData = isNew ? { prop: '', val: '', link: false, linkTarget: '', linkSplit: false, isMediaPreview: false, ref: {}, isPersonOrg: false, socials: {} } : dataItems[index];
             var currentData = isNew ? { prop: '', val: '', link: false, linkTarget: '', linkSplit: false, isMedia: false, ref: {}, isPersonOrg: false, socials: {} } : dataItems[index];
              
              
             if (isNew && draftData) {
             if (isNew && draftData) {
Baris 591: Baris 588:
             $form.append($propGroup);
             $form.append($propGroup);
              
              
            // --- FITUR BARU: Pratinjau Autocomplete Wikilink ---
             var $valBox = $('<div class="md-form-group"></div>');
             var $valBox = $('<div class="md-form-group"></div>');
             $valBox.append('<label>Nilai Data <span class="md-help-icon" data-help="Isi dari data tersebut. Ketik kata kunci, kami akan memunculkan rekomendasi artikel yang sudah ada di Wiki ini!">' + svgHelp + '</span></label>');
             $valBox.append('<label>Nilai Data <span class="md-help-icon" data-help="Isi dari data tersebut. Ketik huruf pertama untuk memunculkan pratinjau halaman wiki secara otomatis.">' + svgHelp + '</span></label>');
           
            var $valInput = $('<input type="text" id="md-inp-val" class="md-input" autocomplete="off" value="' + sanitizeHTML(currentData.val || '') + '">');
            var $autoCompleteBox = $('<div class="md-autocomplete-list"></div>');
           
            $valBox.append($valInput).append($autoCompleteBox);
           
            var $wikilinkOpts = $('<div style="margin-top:8px; display:flex; flex-direction:column; gap:8px; background:#f8fafc; padding:10px; border-radius:8px; border:1px solid #e2e8f0;"></div>');
            var chkLink = currentData.link ? 'checked' : '';
            var chkSplit = currentData.linkSplit ? 'checked' : '';
            var chkMedia = currentData.isMedia ? 'checked' : '';
           
            $wikilinkOpts.append('<label style="display:flex; align-items:center; gap:8px; font-size:0.82rem; font-weight:700; cursor:pointer;"><input type="checkbox" id="md-inp-link" ' + chkLink + '> Jadikan Wikilink Standar</label>');
            $wikilinkOpts.append('<label style="display:flex; align-items:center; gap:8px; font-size:0.82rem; font-weight:700; cursor:pointer;"><input type="checkbox" id="md-inp-split" ' + chkSplit + '> Smart Split Wikilink</label>');
           
            // Opsi Pratinjau Media Baru
            $wikilinkOpts.append('<hr style="border:0; border-top:1px dashed #cbd5e1; margin:4px 0;"><label style="display:flex; align-items:center; gap:8px; font-size:0.82rem; font-weight:800; color:#3730a3; cursor:pointer;"><input type="checkbox" id="md-inp-media" ' + chkMedia + '> Render URL Sbg Pratinjau Gambar/Video/Musik <span class="md-help-icon" data-help="Jika ini dicentang dan Nilai Data adalah URL Gambar atau YouTube/Spotify, sistem akan otomatis merendernya menjadi gambar atau pemutar langsung.">' + svgHelp + '</span></label>');
              
              
             var $autoWrap = $('<div class="md-autocomplete-wrapper"></div>');
             var $pipedGroup = $('<div style="margin-top:4px;"><label style="font-size:0.78rem; color:#475569; margin-bottom:4px;">Target Halaman Khusus (Opsional):</label><input type="text" id="md-inp-target" class="md-input" style="padding:6px 10px; font-size:0.85rem;" placeholder="Contoh: Diskografi Rumi Haitami" value="' + sanitizeHTML(currentData.linkTarget || '') + '"></div>');
            var $valInput = $('<input type="text" id="md-inp-val" class="md-input" autocomplete="off" placeholder="Ketik nilai atau cari halaman di Wiki..." value="' + sanitizeHTML(currentData.val || '') + '">');
             $wikilinkOpts.append($pipedGroup);
            var $autoList = $('<div class="md-autocomplete-list"></div>');
            $valBox.append($wikilinkOpts);
             $autoWrap.append($valInput).append($autoList);
             $form.append($valBox);
             $valBox.append($autoWrap);


             var typingTimer;
             // AUTOCOMPLETE & SMART MEDIA URL DETECTOR
             var lastSelectedTitle = currentData.val || "";
             var acTimer;
           
             $valInput.on('input', function() {
             $valInput.on('input', function() {
                 var q = $(this).val().trim();
                 var v = $(this).val();
                  
                  
                 // Jika input diubah tidak sesuai dengan yang di-select sebelumnya, uncheck Wikilink Standar
                 // Smart Media URL Detector
                 if (q !== lastSelectedTitle) {
                if (v.match(/\.(jpeg|jpg|gif|png|webp|bmp)$/i) || v.match(/(youtube\.com|youtu\.be|spotify\.com)/i)) {
                    $('#md-inp-media').prop('checked', true);
                }
 
                // Autocomplete Logic
                clearTimeout(acTimer);
                 if (v.length < 1) {
                    $autoCompleteBox.hide();
                     $('#md-inp-link').prop('checked', false);
                     $('#md-inp-link').prop('checked', false);
                    return;
                 }
                 }
                  
                  
                 clearTimeout(typingTimer);
                 acTimer = setTimeout(function() {
                if (q.length < 1) { $autoList.hide(); return; }
                     api.get({ action: 'opensearch', search: v, limit: 6, namespace: 0, format: 'json' }).done(function(data) {
               
                         var titles = data[1];
                typingTimer = setTimeout(function() {
                         $autoCompleteBox.empty();
                    // API Call ke Mediawiki untuk saran halaman
                         if (titles.length > 0) {
                     api.get({ action: 'opensearch', search: q, limit: 7, format: 'json' }).done(function(res) {
                         var titles = res[1];
                         $autoList.empty();
                         if (titles && titles.length > 0) {
                             titles.forEach(function(t) {
                             titles.forEach(function(t) {
                                 var $item = $('<div class="md-autocomplete-item">' + svgSearch + ' ' + sanitizeHTML(t) + '</div>');
                                 var $item = $('<div class="md-autocomplete-item">' + sanitizeHTML(t) + '</div>');
                                 $item.on('click', function() {
                                 $item.on('click', function() {
                                     $valInput.val(t);
                                     $valInput.val(t);
                                    lastSelectedTitle = t;
                                     $('#md-inp-link').prop('checked', true);
                                     $('#md-inp-link').prop('checked', true); // AUTO-CENTANG
                                     $autoCompleteBox.hide();
                                     $autoList.hide();
                                 });
                                 });
                                 $autoList.append($item);
                                 $autoCompleteBox.append($item);
                             });
                             });
                             $autoList.show();
                             $autoCompleteBox.show();
                         } else {
                         } else { $autoCompleteBox.hide(); }
                            $autoList.hide();
                        }
                     });
                     });
                 }, 400); // debounce
                 }, 300);
            });
 
            // Klik di luar daftar hilangkan autocomplete
            $(document).on('click.auto', function(e) {
                if (!$(e.target).closest('.md-autocomplete-wrapper').length) $autoList.hide();
             });
             });
 
             $(document).on('click.mdac', function(e) { if(!$(e.target).closest($valBox).length) $autoCompleteBox.hide(); });
             var $wikilinkOpts = $('<div style="margin-top:8px; display:flex; flex-direction:column; gap:8px; background:#f8fafc; padding:10px; border-radius:8px; border:1px solid #e2e8f0;"></div>');
            var chkLink = currentData.link ? 'checked' : '';
            var chkSplit = currentData.linkSplit ? 'checked' : '';
            var chkMedia = currentData.isMediaPreview ? 'checked' : '';
           
            $wikilinkOpts.append('<label style="display:flex; align-items:center; gap:8px; font-size:0.82rem; font-weight:700; cursor:pointer;"><input type="checkbox" id="md-inp-link" ' + chkLink + '> Jadikan Wikilink Standar</label>');
            $wikilinkOpts.append('<label style="display:flex; align-items:center; gap:8px; font-size:0.82rem; font-weight:700; cursor:pointer;"><input type="checkbox" id="md-inp-split" ' + chkSplit + '> Smart Split Wikilink</label>');
            $wikilinkOpts.append('<label style="display:flex; align-items:center; gap:8px; font-size:0.82rem; font-weight:700; cursor:pointer; color:#0369a1;"><input type="checkbox" id="md-inp-media" ' + chkMedia + '> ' + svgMedia + ' Jadikan Pratinjau Gambar/Media (Otomatis)</label>');
           
            var $pipedGroup = $('<div style="margin-top:4px;"><label style="font-size:0.78rem; color:#475569; margin-bottom:4px;">Target Halaman Khusus (Opsional):</label><input type="text" id="md-inp-target" class="md-input" style="padding:6px 10px; font-size:0.85rem;" placeholder="Contoh: Diskografi Rumi Haitami" value="' + sanitizeHTML(currentData.linkTarget || '') + '"></div>');
            $wikilinkOpts.append($pipedGroup);
            $valBox.append($wikilinkOpts);
            $form.append($valBox);


             var $socialSec = $('<div class="md-form-group" style="margin-top:16px;"></div>');
             var $socialSec = $('<div class="md-form-group" style="margin-top:16px;"></div>');
             var chkPerson = currentData.isPersonOrg ? 'checked' : '';
             var chkPerson = currentData.isPersonOrg ? 'checked' : '';
             $socialSec.append('<label style="display:flex; align-items:center; gap:8px; cursor:pointer; background:#e0e7ff; padding:10px; border-radius:8px; font-weight:700; color:#3730a3; font-size:0.85rem;"><input type="checkbox" id="md-chk-person" ' + chkPerson + '> Simpan Link Profil Media Sosial & Musik <span class="md-help-icon" data-help="Centang ini jika data ini mengandung identitas. Sistem akan memisahkan tombol sosial media ini secara otomatis ke dalam kotak khusus di panel atas (Header).">' + svgHelp + '</span></label>');
             $socialSec.append('<label style="display:flex; align-items:center; gap:8px; cursor:pointer; background:#e0e7ff; padding:10px; border-radius:8px; font-weight:700; color:#3730a3; font-size:0.85rem;"><input type="checkbox" id="md-chk-person" ' + chkPerson + '> Aktifkan Profil Media Sosial & Musik <span class="md-help-icon" data-help="Media sosial akan otomatis dipisah dan ditampilkan rapi di atas semua rincian data Anda.">' + svgHelp + '</span></label>');
              
              
             var $socialGrid = $('<div class="md-social-grid" id="md-social-box" style="display:' + (currentData.isPersonOrg ? 'grid' : 'none') + '; margin-top:8px;"></div>');
             var $socialGrid = $('<div class="md-social-grid" id="md-social-box" style="display:' + (currentData.isPersonOrg ? 'grid' : 'none') + '; margin-top:8px;"></div>');
Baris 666: Baris 659:
                 var p = SOCIAL_PLATFORMS[key];
                 var p = SOCIAL_PLATFORMS[key];
                 var valUname = currentSocials[key] || '';
                 var valUname = currentSocials[key] || '';
                 $socialGrid.append('<div><label style="font-size:0.78rem; color:#334155; font-weight:700; margin-bottom:4px;">' + p.name + '</label><input type="text" id="md-soc-' + key + '" class="md-input" placeholder="ID / Username / URL" value="' + sanitizeHTML(valUname) + '"></div>');
                 $socialGrid.append('<div><label style="font-size:0.78rem; color:#334155; font-weight:700; margin-bottom:4px;">' + p.name + '</label><input type="text" id="md-soc-' + key + '" class="md-input" placeholder="ID / Username" value="' + sanitizeHTML(valUname) + '"></div>');
             });
             });
             $socialSec.append($socialGrid);
             $socialSec.append($socialGrid);
Baris 672: Baris 665:


             var $refArea = $('<div class="md-form-group" style="margin-top:20px;"></div>');
             var $refArea = $('<div class="md-form-group" style="margin-top:20px;"></div>');
             $refArea.append('<label style="font-size:0.95rem; font-weight:800; color:#0f172a; margin-bottom:10px;">Sumber Referensi <span class="md-help-icon" data-help="Mohon berikan sumber referensi yang valid. Jika URL rujukan Anda mengarah ke situs web besar yang terverifikasi, lencana centang biru akan muncul otomatis.">' + svgHelp + '</span></label>');
             $refArea.append('<label style="font-size:0.95rem; font-weight:800; color:#0f172a; margin-bottom:10px;">Sumber Referensi <span class="md-help-icon" data-help="Jika URL rujukan mengarah ke situs web terverifikasi, lencana centang biru otomatis muncul.">' + svgHelp + '</span></label>');
             var $refGrid = $('<div class="md-ref-grid"></div>');
             var $refGrid = $('<div class="md-ref-grid"></div>');
              
              
Baris 725: Baris 718:
             $form.append(`
             $form.append(`
                 <div class="md-cc0-notice">
                 <div class="md-cc0-notice">
                     <b>Perhatian Lisensi:</b> Setiap data yang diterbitkan di sini akan terlihat publik dan dirilis di bawah lisensi <b>Creative Commons Zero (CC0)</b>. Jika Anda tidak ingin suntingan Anda digunakan secara bebas dan publik tanpa batas, jangan klik Terbitkan. <span class="md-help-icon" data-help="Dengan menerbitkan, Anda melepaskan seluruh hak cipta atas input data ini ke domain publik murni sesuai prinsip Wikidata.">${svgHelp}</span>
                     <b>Perhatian Lisensi:</b> Setiap data yang diterbitkan di sini akan dirilis di bawah lisensi <b>Creative Commons Zero (CC0)</b>.
                 </div>
                 </div>
                 <div class="md-edit-meta">
                 <div class="md-edit-meta">
                     <label style="font-size:0.85rem; font-weight:700;">Ringkasan Suntingan <span class="md-help-icon" data-help="Jelaskan secara singkat apa yang Anda ubah atau tambahkan. Contoh: Menambah data tanggal lahir rumi.">${svgHelp}</span></label>
                     <label style="font-size:0.85rem; font-weight:700;">Ringkasan Suntingan</label>
                     <input type="text" id="md-edit-summary" class="md-input" placeholder="Jelaskan perubahan Anda secara singkat...">
                     <input type="text" id="md-edit-summary" class="md-input" placeholder="Jelaskan perubahan Anda secara singkat...">
                     <label style="font-size:0.85rem; cursor:pointer; font-weight:700; margin-top:5px; display:inline-flex; align-items:center; gap:6px;"><input type="checkbox" id="md-minor-edit"> Ini adalah suntingan kecil (minor)</label>
                     <label style="font-size:0.85rem; cursor:pointer; font-weight:700; margin-top:5px; display:inline-flex; align-items:center; gap:6px;"><input type="checkbox" id="md-minor-edit"> Ini adalah suntingan kecil (minor)</label>
Baris 772: Baris 765:


             $('html, body').animate({ scrollTop: $form.offset().top - 60 }, 250);
             $('html, body').animate({ scrollTop: $form.offset().top - 60 }, 250);
           
             $btnCancel.on('click', function() { $(document).off('click.mdac'); $container.empty(); });
             $btnCancel.on('click', function() {  
                $container.empty();
                $(document).off('click.auto');
            });


             $btnDraft.on('click', function() {
             $btnDraft.on('click', function() {
Baris 792: Baris 781:
                     linkSplit: $('#md-inp-split').is(':checked'),
                     linkSplit: $('#md-inp-split').is(':checked'),
                     linkTarget: $('#md-inp-target').val().trim(),
                     linkTarget: $('#md-inp-target').val().trim(),
                     isMediaPreview: $('#md-inp-media').is(':checked'),
                     isMedia: $('#md-inp-media').is(':checked'),
                     isPersonOrg: $('#md-chk-person').is(':checked'),
                     isPersonOrg: $('#md-chk-person').is(':checked'),
                     socials: socObj,
                     socials: socObj,
Baris 842: Baris 831:
                     linkSplit: $('#md-inp-split').is(':checked'),
                     linkSplit: $('#md-inp-split').is(':checked'),
                     linkTarget: $('#md-inp-target').val().trim(),
                     linkTarget: $('#md-inp-target').val().trim(),
                     isMediaPreview: $('#md-inp-media').is(':checked'),
                     isMedia: $('#md-inp-media').is(':checked'),
                     isPersonOrg: isPerson,
                     isPersonOrg: isPerson,
                     socials: socObj,
                     socials: socObj,