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 (V8.0 ULTIMATE)
  * MIPPEDIA DATA - ADVANCED STRUCTURED DATA SYSTEM (V8.5 SECURE ULTIMATE)
  * Fitur: ALL ORIGINAL V7 FEATURES KEEPT + Collapsible Ref, Verified Ticks,  
  * Fitur: ALL ORIGINAL V7/V8 FEATURES KEPT + Responsive Verified Ticks,  
  * Help Tooltips, Local Draft, Edit Summary, CC0 Licensing, Lock UI
  * Smart Tooltip Position, Advanced XSS Security, Detailed Ref Instructions.
  * ====================================================================
  * ====================================================================
  */
  */
Baris 38: Baris 38:
         };
         };


         // 2. PRESET & MASTER RUJUKAN (FULL ASLI)
         // 2. PRESET & MASTER RUJUKAN
         var PRESET_LABELS = [
         var PRESET_LABELS = [
             "Nama Lahir", "Tanggal Lahir", "Tempat Lahir", "Pekerjaan",  
             "Nama Lahir", "Tanggal Lahir", "Tempat Lahir", "Pekerjaan",  
Baris 60: Baris 60:
         };
         };


        // DOMAIN TERVERIFIKASI (FITUR BARU)
         var VERIFIED_DOMAINS = [
         var VERIFIED_DOMAINS = [
             'kompas.com', 'kompas.id', 'detik.com', 'cnnindonesia.com', 'tribunnews.com', 'kumparan.com', 'tirto.id',
             'kompas.com', 'kompas.id', 'detik.com', 'cnnindonesia.com', 'tribunnews.com', 'kumparan.com', 'tirto.id',
Baris 67: Baris 66:
         ];
         ];


         // 3. IKON SISTEM (FULL ASLI + BARU)
         // 3. IKON SISTEM  
         var svgEdit = '<svg class="md-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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>';
         var svgEdit = '<svg class="md-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><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>';
         var svgLock = '<svg class="md-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>';
         var svgLock = '<svg class="md-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"></rect><path d="M7 11V7a5 5 0 0 1 10 0v4"></path></svg>';
Baris 78: Baris 77:
         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>';


         // 4. HELPER UTILITY
         // 4. HELPER UTILITY & ADVANCED SECURITY
        function sanitizeHTML(str) {
            if (typeof str !== 'string') return str;
            return str.replace(/[&<>"']/g, function(m) {
                return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[m];
            });
        }
         function isVerifiedSite(urlStr) {
         function isVerifiedSite(urlStr) {
             if(!urlStr) return false;
             if(!urlStr) return false;
Baris 92: Baris 97:
             var monthIdx = parseInt(parts[1], 10) - 1;
             var monthIdx = parseInt(parts[1], 10) - 1;
             var year = parts[0];
             var year = parts[0];
             if (isNaN(day) || monthIdx < 0 || monthIdx > 11 || isNaN(year)) return dateStr;
             if (isNaN(day) || monthIdx < 0 || monthIdx > 11 || isNaN(year)) return sanitizeHTML(dateStr);
             return day + ' ' + months[monthIdx] + ' ' + year;
             return day + ' ' + months[monthIdx] + ' ' + year;
         }
         }
Baris 104: Baris 109:
             if (!urlStr) return '';
             if (!urlStr) return '';
             urlStr = urlStr.trim();
             urlStr = urlStr.trim();
            // Block javascript/vbscript execution vulnerability in URL fields
            if (/^(javascript|data|vbscript):/i.test(urlStr)) return '';
             return (urlStr && !urlStr.match(/^https?:\/\//i)) ? 'https://' + urlStr : urlStr;
             return (urlStr && !urlStr.match(/^https?:\/\//i)) ? 'https://' + urlStr : urlStr;
         }
         }
Baris 114: Baris 121:
         }
         }


         // 5. STYLESHEET (GABUNGAN LAMA + BARU)
         // 5. STYLESHEET  
         var css = `
         var css = `
             #mippedia-data-app {
             #mippedia-data-app {
Baris 150: Baris 157:
             .md-btn-copy:hover { color: #4f46e5; background: #e0e7ff; }
             .md-btn-copy:hover { color: #4f46e5; background: #e0e7ff; }


            /* MEDIA SOSIAL BADGES ASLI */
             .md-social-group { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
             .md-social-group { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
             .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 { 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-social-badge:hover { transform: translateY(-2px); opacity: 0.92; color: white; text-decoration: none; }


            /* REFERENSI AKORDEON BARU */
             .md-ref-container { margin-top: 10px; width: 100%; box-sizing: border-box; }
             .md-ref-container { margin-top: 10px; width: 100%; box-sizing: border-box; }
             .md-ref-details { background: #f8fafc; border-left: 3px solid #4f46e5; border-radius: 10px; overflow: hidden; border-top:1px solid #f1f5f9; border-right:1px solid #f1f5f9; border-bottom:1px solid #f1f5f9; transition: 0.3s; }
             .md-ref-details { background: #f8fafc; border-left: 3px solid #4f46e5; border-radius: 10px; overflow: hidden; border-top:1px solid #f1f5f9; border-right:1px solid #f1f5f9; border-bottom:1px solid #f1f5f9; transition: 0.3s; }
             .md-ref-details summary { display: flex; align-items: center; padding: 10px 14px; font-size: 0.85rem; font-weight: 700; color: #374151; cursor: pointer; user-select: none; list-style: none; gap: 8px; flex-wrap:wrap; }
             .md-ref-details summary { display: flex; padding: 10px 14px; font-size: 0.85rem; font-weight: 700; color: #374151; cursor: pointer; user-select: none; list-style: none; flex-wrap: nowrap; }
             .md-ref-details summary::-webkit-details-marker { display: none; }
             .md-ref-details summary::-webkit-details-marker { display: none; }
             .md-ref-details[open] summary { border-bottom: 1px dashed #cbd5e1; background: #e0e7ff; color: #3730a3; }
             .md-ref-details[open] summary { border-bottom: 1px dashed #cbd5e1; background: #e0e7ff; color: #3730a3; }
             .md-ref-body { padding: 12px 14px; font-size: 0.85rem; display: flex; flex-direction: column; gap: 6px; }
             .md-ref-body { padding: 12px 14px; font-size: 0.85rem; display: flex; flex-direction: column; gap: 6px; }
              
              
             .md-verified-badge { cursor: pointer; display: inline-flex; align-items: center; justify-content: center; margin-left: 5px; vertical-align: middle; transition: transform 0.2s; }
             .md-verified-badge { cursor: pointer; display: inline-flex; align-items: center; justify-content: center; margin-left: 2px; vertical-align: middle; transition: transform 0.2s; flex-shrink: 0; }
             .md-verified-badge:hover { transform: scale(1.1); }
             .md-verified-badge:hover { transform: scale(1.1); }
             .md-btn-visit { background: #f1f5f9; color: #0f172a; padding: 6px 12px; border-radius: 6px; text-decoration: none; font-size: 0.8rem; display: inline-block; width: fit-content; margin-top: 6px; font-weight:700; border:1px solid #cbd5e1; }
             .md-btn-visit { background: #f1f5f9; color: #0f172a; padding: 6px 12px; border-radius: 6px; text-decoration: none; font-size: 0.8rem; display: inline-block; width: fit-content; margin-top: 6px; font-weight:700; border:1px solid #cbd5e1; }
             .md-btn-visit:hover { background: #e2e8f0; text-decoration:none; }
             .md-btn-visit:hover { background: #e2e8f0; text-decoration:none; }


            /* PRESET PILL & FORMS ASLI + BARU */
             .md-preset-wrapper { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
             .md-preset-wrapper { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
             .md-preset-pill { background: #f1f5f9; border: 1px solid #cbd5e1; padding: 4px 10px; border-radius: 12px; font-size: 0.75rem; font-weight: 600; color: #475569; cursor: pointer; transition: 0.2s; }
             .md-preset-pill { background: #f1f5f9; border: 1px solid #cbd5e1; padding: 4px 10px; border-radius: 12px; font-size: 0.75rem; font-weight: 600; color: #475569; cursor: pointer; transition: 0.2s; }
Baris 184: Baris 188:
             .md-grid-full { grid-column: span 2; }
             .md-grid-full { grid-column: span 2; }
              
              
            /* Peringatan & Tooltips Baru */
             .md-cc0-notice { background: #fef2f2; color: #991b1b; padding: 12px; border-radius: 8px; font-size: 0.82rem; line-height: 1.4; border-left: 4px solid #ef4444; margin-top: 15px; margin-bottom: 15px; }
             .md-cc0-notice { background: #fef2f2; color: #991b1b; padding: 12px; border-radius: 8px; font-size: 0.82rem; line-height: 1.4; border-left: 4px solid #ef4444; margin-top: 15px; margin-bottom: 15px; }
             .md-edit-meta { background: #f8fafc; padding: 12px; border-radius: 8px; margin-bottom: 15px; border: 1px solid #e2e8f0; display:flex; flex-direction:column; gap:10px; }
             .md-edit-meta { background: #f8fafc; padding: 12px; border-radius: 8px; margin-bottom: 15px; border: 1px solid #e2e8f0; display:flex; flex-direction:column; gap:10px; }
Baris 190: Baris 193:
             .md-help-icon { color: #64748b; cursor: pointer; margin-left: 6px; display:inline-flex; align-items:center; }
             .md-help-icon { color: #64748b; cursor: pointer; margin-left: 6px; display:inline-flex; align-items:center; }
             .md-help-icon:hover { color: #4f46e5; }
             .md-help-icon:hover { color: #4f46e5; }
             .md-tooltip-popup { position: absolute; background: #1e293b; color: #fff; padding: 10px 14px; border-radius: 8px; font-size: 0.8rem; width: 240px; z-index: 1000; box-shadow: 0 4px 15px rgba(0,0,0,0.15); display: none; line-height: 1.4; text-align: left; font-weight:normal; }
             .md-tooltip-popup { position: absolute; background: #1e293b; color: #fff; padding: 10px 14px; border-radius: 8px; font-size: 0.8rem; width: 260px; max-width: calc(100vw - 30px); z-index: 1000; box-shadow: 0 4px 15px rgba(0,0,0,0.15); display: none; line-height: 1.4; text-align: left; font-weight:normal; }
            /* Memastikan panah selalu berada di tempat wajar meski boks bergeser di mobile */
             .md-tooltip-popup::after { content: ''; position: absolute; border-width: 6px; border-style: solid; border-color: #1e293b transparent transparent transparent; top: 100%; left: 15px; }
             .md-tooltip-popup::after { content: ''; position: absolute; border-width: 6px; border-style: solid; border-color: #1e293b transparent transparent transparent; top: 100%; left: 15px; }


Baris 225: Baris 229:
         $('<style>').text(css).appendTo('head');
         $('<style>').text(css).appendTo('head');


         // 6. POPUP, MODAL & TOOLTIPS
         // 6. POPUP, MODAL & TOOLTIPS (FIXED POSITION & SECURE)
         function showModal(title, text) {
         function showModal(title, text) {
             $('.md-modal-overlay').remove();
             $('.md-modal-overlay').remove();
             var $overlay = $('<div class="md-modal-overlay"></div>');
             var $overlay = $('<div class="md-modal-overlay"></div>');
             var $box = $('<div class="md-modal-box"><h3 style="margin-top:0; color:#0f172a;">' + title + '</h3><p style="font-size:0.9rem; color:#475569; margin:15px 0;">' + text + '</p><button class="md-btn md-btn-save" style="width:100%;">Tutup & Mengerti</button></div>');
             var $box = $('<div class="md-modal-box"><h3 style="margin-top:0; color:#0f172a;">' + sanitizeHTML(title) + '</h3><p style="font-size:0.9rem; color:#475569; margin:15px 0;">' + sanitizeHTML(text) + '</p><button class="md-btn md-btn-save" style="width:100%;">Tutup & Mengerti</button></div>');
             $box.find('button').on('click', function() { $overlay.remove(); });
             $box.find('button').on('click', function() { $overlay.remove(); });
             $overlay.append($box);
             $overlay.append($box);
Baris 240: Baris 244:
                 e.stopPropagation();
                 e.stopPropagation();
                 var $icon = $(this);
                 var $icon = $(this);
                 var text = $icon.data('help');
                // Menggunakan sanitize untuk mengamankan atribut sebelum diletakkan di DOM
                 var text = sanitizeHTML($icon.data('help'));  
                 var $tt = $('<div class="md-tooltip-popup">' + text + '</div>');
                 var $tt = $('<div class="md-tooltip-popup">' + text + '</div>');
                 $('body').append($tt);
                 $('body').append($tt);
               
                 var offset = $icon.offset();
                 var offset = $icon.offset();
                 // Posisi di atas icon dengan panah menunjuk ke bawah
                var ttWidth = $tt.outerWidth();
                 $tt.css({ top: offset.top - $tt.outerHeight() - 10, left: offset.left - 15 }).fadeIn(200);
                var winWidth = $(window).width();
                var leftPos = offset.left - 15;
               
                 // Mencegah tooltip terpotong di tepi kanan layar (Smart Shift Position)
                if (leftPos + ttWidth > winWidth - 15) {
                    leftPos = winWidth - ttWidth - 15;
                }
                // Mencegah terpotong di tepi kiri
                if (leftPos < 10) {
                    leftPos = 10;
                }
               
                 $tt.css({ top: offset.top - $tt.outerHeight() - 10, left: leftPos }).fadeIn(200);
             });
             });
             $(document).off('click.mdtt').on('click.mdtt', function() { $('.md-tooltip-popup').fadeOut(200, function(){ $(this).remove(); }); });
             $(document).off('click.mdtt').on('click.mdtt', function() { $('.md-tooltip-popup').fadeOut(200, function(){ $(this).remove(); }); });
         }
         }


         // 7. PARSER WIKITEXT & PENYIMPANAN DATA (ASLI UTUH)
         // 7. PARSER WIKITEXT & PENYIMPANAN DATA  
         function loadData() {
         function loadData() {
             return api.get({
             return api.get({
Baris 306: Baris 324:
             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 V8: ' + actionType);
             var summaryTxt = customSummary ? ('Mippedia Data: ' + customSummary) : ('Mippedia Data V8.5: ' + 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 329: Baris 347:
                 action: 'edit', title: pageName, text: newContent, summary: summaryTxt, minor: isMinorEdit || false
                 action: 'edit', title: pageName, text: newContent, summary: summaryTxt, minor: isMinorEdit || false
             }).done(function() {
             }).done(function() {
                 localStorage.removeItem(DRAFT_KEY); // Hapus draft jika sukses
                 localStorage.removeItem(DRAFT_KEY);
                 var nocacheUrl = location.pathname + "?refresh=" + new Date().getTime();
                 var nocacheUrl = location.pathname + "?refresh=" + new Date().getTime();
                 window.location.href = nocacheUrl;
                 window.location.href = nocacheUrl;
Baris 387: Baris 405:
                     var $row = $('<div class="md-row-display"></div>');
                     var $row = $('<div class="md-row-display"></div>');
                     var $info = $('<div class="md-data-group"></div>');
                     var $info = $('<div class="md-data-group"></div>');
                     $info.append('<span class="md-data-label">' + item.prop + '</span>');
                     $info.append('<span class="md-data-label">' + sanitizeHTML(item.prop) + '</span>');
                      
                      
                     var displayValHtml = '';
                     var displayValHtml = '';
Baris 393: Baris 411:


                     if (rawVal.match(/^https?:\/\//i)) {
                     if (rawVal.match(/^https?:\/\//i)) {
                         displayValHtml = '<a href="' + rawVal + '" target="_blank" style="color:#2563eb; font-weight:700; text-decoration:none;">' + extractDomain(rawVal) + '</a>';
                         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() !== '') {
                     } 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;">' + 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 (item.linkSplit) {
                     } else if (item.linkSplit) {
                         var parts = rawVal.split(/([,/\s]+dan\s+|[,/\s]+atau\s+|[,/]+)/gi);
                         var parts = rawVal.split(/([,/\s]+dan\s+|[,/\s]+atau\s+|[,/]+)/gi);
Baris 401: Baris 419:
                             var trimmed = p.trim();
                             var trimmed = p.trim();
                             if (trimmed && !p.match(/^[,/\s]+$/) && trimmed.toLowerCase() !== 'dan' && trimmed.toLowerCase() !== 'atau') {
                             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;">' + trimmed + '</a>';
                                 return '<a href="' + mw.util.getUrl(trimmed) + '" style="color:#2563eb; font-weight:700; text-decoration:none;">' + sanitizeHTML(trimmed) + '</a>';
                             }
                             }
                             return p;
                             return sanitizeHTML(p);
                         }).join('');
                         }).join('');
                     } else if (item.link) {
                     } else if (item.link) {
                         displayValHtml = '<a href="' + mw.util.getUrl(rawVal) + '" style="color:#2563eb; font-weight:700; text-decoration:none;">' + rawVal + '</a>';
                         displayValHtml = '<a href="' + mw.util.getUrl(rawVal) + '" style="color:#2563eb; font-weight:700; text-decoration:none;">' + sanitizeHTML(rawVal) + '</a>';
                     } else {
                     } else {
                         displayValHtml = rawVal;
                         displayValHtml = sanitizeHTML(rawVal);
                     }
                     }


Baris 423: Baris 441:
                     $info.append($valWrap);
                     $info.append($valWrap);


                    // MEDIA SOSIAL LENGKAP
                     if (item.isPersonOrg && item.socials) {
                     if (item.isPersonOrg && item.socials) {
                         var $socialGroup = $('<div class="md-social-group"></div>');
                         var $socialGroup = $('<div class="md-social-group"></div>');
Baris 433: Baris 450:
                                 var platform = SOCIAL_PLATFORMS[key];
                                 var platform = SOCIAL_PLATFORMS[key];
                                 var fullUrl = platform.base + uname.replace(/^@/, '');
                                 var fullUrl = platform.base + uname.replace(/^@/, '');
                                 var $badge = $('<a class="md-social-badge" href="' + fullUrl + '" target="_blank" style="background:' + platform.color + ';">' + platform.icon + ' ' + 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);
                                 $socialGroup.append($badge);
                             }
                             }
Baris 440: Baris 457:
                     }
                     }
                      
                      
                    // REFERENSI AKORDEON + CENTANG BIRU
                     if (item.ref && (item.ref.url || item.ref.title)) {
                     if (item.ref && (item.ref.url || item.ref.title)) {
                         var ref = item.ref;
                         var ref = item.ref;
Baris 452: Baris 468:
                         var $refDetails = $(`
                         var $refDetails = $(`
                             <details class="md-ref-details">
                             <details class="md-ref-details">
                                 <summary>
                                 <summary style="align-items:flex-start; gap:8px;">
                                     <span style="color:#4f46e5; display:flex; align-items:center; gap:5px;">${svgRef} REFERENSI:</span>  
                                     <span style="color:#4f46e5; display:inline-flex; align-items:center; gap:5px; white-space:nowrap;">${svgRef} REFERENSI:</span>  
                                     <span style="color:#374151;">${refTitle}</span> ${verBadgeHtml}
                                     <span style="color:#374151; display:inline-flex; align-items:center; gap:4px; flex-wrap:wrap; flex:1;">
                                        <span style="word-break:break-word;">${sanitizeHTML(refTitle)}</span>${verBadgeHtml}
                                    </span>
                                 </summary>
                                 </summary>
                                 <div class="md-ref-body">
                                 <div class="md-ref-body">
                                     ${ref.author ? '<div><span style="color:#64748b; font-weight:700;">Penulis:</span> ' + ref.author + '</div>' : ''}
                                     ${ref.author ? '<div><span style="color:#64748b; font-weight:700;">Penulis:</span> ' + sanitizeHTML(ref.author) + '</div>' : ''}
                                     ${ref.site ? '<div><span style="color:#64748b; font-weight:700;">Situs:</span> ' + ref.site + '</div>' : ''}
                                     ${ref.site ? '<div><span style="color:#64748b; font-weight:700;">Situs:</span> ' + sanitizeHTML(ref.site) + '</div>' : ''}
                                     ${ref.publisher ? '<div><span style="color:#64748b; font-weight:700;">Penerbit:</span> ' + ref.publisher + '</div>' : ''}
                                     ${ref.publisher ? '<div><span style="color:#64748b; font-weight:700;">Penerbit:</span> ' + sanitizeHTML(ref.publisher) + '</div>' : ''}
                                     ${ref.date ? '<div><span style="color:#64748b; font-weight:700;">Tanggal Akses:</span> ' + formatIndonesianDate(ref.date) + '</div>' : ''}
                                     ${ref.date ? '<div><span style="color:#64748b; font-weight:700;">Tanggal Akses:</span> ' + formatIndonesianDate(ref.date) + '</div>' : ''}
                                     ${ref.type ? '<div style="margin-top:4px;"><span style="background:#e2e8f0; padding:2px 8px; border-radius:6px; font-weight:700; font-size:0.75rem;">' + ref.type + (ref.subType ? ' &bull; ' + ref.subType : '') + '</span></div>' : ''}
                                     ${ref.type ? '<div style="margin-top:4px;"><span style="background:#e2e8f0; padding:2px 8px; border-radius:6px; font-weight:700; font-size:0.75rem;">' + sanitizeHTML(ref.type) + (ref.subType ? ' &bull; ' + sanitizeHTML(ref.subType) : '') + '</span></div>' : ''}
                                     ${safeUrl ? '<a href="' + safeUrl + '" target="_blank" class="md-btn-visit">Website</a>' : ''}
                                     ${safeUrl ? '<a href="' + sanitizeHTML(safeUrl) + '" target="_blank" rel="noopener noreferrer" class="md-btn-visit">Website</a>' : ''}
                                 </div>
                                 </div>
                             </details>
                             </details>
Baris 507: Baris 525:
             var isNew = index === -1;
             var isNew = index === -1;
              
              
            // Cek Draft Lokal
             var draftData = localStorage.getItem(DRAFT_KEY);
             var draftData = localStorage.getItem(DRAFT_KEY);
             var currentData = isNew ? { prop: '', val: '', link: false, linkTarget: '', linkSplit: false, ref: {}, isPersonOrg: false, socials: {} } : dataItems[index];
             var currentData = isNew ? { prop: '', val: '', link: false, linkTarget: '', linkSplit: false, ref: {}, isPersonOrg: false, socials: {} } : dataItems[index];
Baris 523: Baris 540:
             $form.append('<h4 class="md-form-title">' + (isNew ? 'Tambah Data Baru' : 'Edit Data Terstruktur') + '</h4>');
             $form.append('<h4 class="md-form-title">' + (isNew ? 'Tambah Data Baru' : 'Edit Data Terstruktur') + '</h4>');
              
              
            // Label Data
             var $propGroup = $('<div class="md-form-group"></div>');
             var $propGroup = $('<div class="md-form-group"></div>');
             $propGroup.append('<label>Label Data <span class="md-help-icon" data-help="Nama bidang data (contoh: Pekerjaan, Sutradara, Tempat Lahir). Klik tombol preset di bawah untuk isi otomatis.">' + svgHelp + '</span></label>');
             $propGroup.append('<label>Label Data <span class="md-help-icon" data-help="Nama bidang data (contoh: Pekerjaan, Sutradara, Tempat Lahir). Klik tombol preset di bawah untuk isi otomatis.">' + svgHelp + '</span></label>');
             $propGroup.append('<input type="text" id="md-inp-prop" class="md-input" value="' + (currentData.prop || '') + '">');
             $propGroup.append('<input type="text" id="md-inp-prop" class="md-input" value="' + sanitizeHTML(currentData.prop || '') + '">');
              
              
             var $presetWrap = $('<div class="md-preset-wrapper"></div>');
             var $presetWrap = $('<div class="md-preset-wrapper"></div>');
Baris 537: Baris 553:
             $form.append($propGroup);
             $form.append($propGroup);
              
              
            // Nilai Data & Wikilink Options
             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 (contoh: Penyanyi, 15 April 2003). Centang opsi di bawah jika ingin jadikan teks ini sebagai link biru (Wikilink).">' + svgHelp + '</span></label>');
             $valBox.append('<label>Nilai Data <span class="md-help-icon" data-help="Isi dari data tersebut (contoh: Penyanyi, 15 April 2003). Centang opsi di bawah jika ingin jadikan teks ini sebagai link biru (Wikilink).">' + svgHelp + '</span></label>');
             $valBox.append('<input type="text" id="md-inp-val" class="md-input" value="' + (currentData.val || '') + '">');
             $valBox.append('<input type="text" id="md-inp-val" class="md-input" value="' + sanitizeHTML(currentData.val || '') + '">');
              
              
             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 $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>');
Baris 548: Baris 563:
             $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-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;"><input type="checkbox" id="md-inp-split" ' + chkSplit + '> Smart Split Wikilink</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="' + (currentData.linkTarget || '') + '"></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>');
             $wikilinkOpts.append($pipedGroup);
             $wikilinkOpts.append($pipedGroup);
             $valBox.append($wikilinkOpts);
             $valBox.append($wikilinkOpts);
             $form.append($valBox);
             $form.append($valBox);


            // MEDIA SOSIAL FULL
             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' : '';
Baris 562: Baris 576:
                 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" value="' + 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);
             $form.append($socialSec);
             $form.append($socialSec);


            // RUJUKAN FULL
             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="Isi data rujukan di sini. Jika URL mengarah ke situs populer, otomatis akan muncul lencana centang biru.">' + svgHelp + '</span></label>');
            // INSTRUKSI REFERENSI LENGKAP DAN DETAIL
             $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, kredibel, dan dapat dipertanggungjawabkan (seperti portal berita resmi, jurnal akademik ilmiah, buku cetak ber-ISBN, atau pangkalan data terverifikasi). Referensi yang kuat, faktual, dan jelas sangat membantu memastikan keakuratan informasi dan mencegah penyebaran misinformasi di platform ini. Jika URL rujukan Anda mengarah ke situs web besar atau domain resmi yang terverifikasi (contoh: Kompas, YouTube, Spotify, BBC, situs pemerintah, dll), sistem keamanan akan secara otomatis menampilkan lencana centang biru sebagai tanda sumber tepercaya.">' + svgHelp + '</span></label>');
             var $refGrid = $('<div class="md-ref-grid"></div>');
             var $refGrid = $('<div class="md-ref-grid"></div>');
              
              
Baris 594: Baris 608:
             $refGrid.append('<div><label style="font-size:0.78rem; margin-bottom:4px;">Sub-Tipe</label></div>').find('div').last().append($subTypeSel);
             $refGrid.append('<div><label style="font-size:0.78rem; margin-bottom:4px;">Sub-Tipe</label></div>').find('div').last().append($subTypeSel);


             var $urlInput = $('<input type="url" id="md-ref-url" class="md-input" placeholder="https://...." value="' + (currentRef.url || '') + '">');
             var $urlInput = $('<input type="url" id="md-ref-url" class="md-input" placeholder="https://...." value="' + sanitizeHTML(currentRef.url || '') + '">');
             $urlInput.on('blur', function() {
             $urlInput.on('blur', function() {
                 var u = ensureHttps($(this).val());
                 var u = ensureHttps($(this).val());
Baris 603: Baris 617:


             $refGrid.append('<div class="md-grid-full"><label style="font-size:0.78rem; margin-bottom:4px;">URL Rujukan</label></div>').find('div').last().append($urlInput);
             $refGrid.append('<div class="md-grid-full"><label style="font-size:0.78rem; margin-bottom:4px;">URL Rujukan</label></div>').find('div').last().append($urlInput);
             $refGrid.append('<div><label style="font-size:0.78rem; margin-bottom:4px;">Judul Halaman</label><input type="text" id="md-ref-title" class="md-input" placeholder="Judul Rujukan" value="' + (currentRef.title || '') + '"></div>');
             $refGrid.append('<div><label style="font-size:0.78rem; margin-bottom:4px;">Judul Halaman</label><input type="text" id="md-ref-title" class="md-input" placeholder="Judul Rujukan" value="' + sanitizeHTML(currentRef.title || '') + '"></div>');
             $refGrid.append('<div><label style="font-size:0.78rem; margin-bottom:4px;">Nama Situs</label><input type="text" id="md-ref-site" class="md-input" placeholder="Situs" value="' + (currentRef.site || '') + '"></div>');
             $refGrid.append('<div><label style="font-size:0.78rem; margin-bottom:4px;">Nama Situs</label><input type="text" id="md-ref-site" class="md-input" placeholder="Situs" value="' + sanitizeHTML(currentRef.site || '') + '"></div>');
             $refGrid.append('<div><label style="font-size:0.78rem; margin-bottom:4px;">Penulis</label><input type="text" id="md-ref-author" class="md-input" placeholder="Penulis" value="' + (currentRef.author || '') + '"></div>');
             $refGrid.append('<div><label style="font-size:0.78rem; margin-bottom:4px;">Penulis</label><input type="text" id="md-ref-author" class="md-input" placeholder="Penulis" value="' + sanitizeHTML(currentRef.author || '') + '"></div>');
             $refGrid.append('<div><label style="font-size:0.78rem; margin-bottom:4px;">Penerbit</label><input type="text" id="md-ref-publisher" class="md-input" placeholder="Penerbit" value="' + (currentRef.publisher || '') + '"></div>');
             $refGrid.append('<div><label style="font-size:0.78rem; margin-bottom:4px;">Penerbit</label><input type="text" id="md-ref-publisher" class="md-input" placeholder="Penerbit" value="' + sanitizeHTML(currentRef.publisher || '') + '"></div>');
              
              
             var $dateBox = $('<div><label style="font-size:0.78rem; margin-bottom:4px;">Tanggal Akses</label></div>');
             var $dateBox = $('<div><label style="font-size:0.78rem; margin-bottom:4px;">Tanggal Akses</label></div>');
             var $dateInpWrap = $('<div style="display:flex; gap:6px;"></div>');
             var $dateInpWrap = $('<div style="display:flex; gap:6px;"></div>');
             var $dateInp = $('<input type="date" id="md-ref-date" class="md-input" value="' + (currentRef.date || '') + '">');
             var $dateInp = $('<input type="date" id="md-ref-date" class="md-input" value="' + sanitizeHTML(currentRef.date || '') + '">');
             var $todayBtn = $('<button type="button" class="md-btn-icon" style="padding:4px 8px; font-size:0.75rem;" title="Set Hari Ini">Hari Ini</button>');
             var $todayBtn = $('<button type="button" class="md-btn-icon" style="padding:4px 8px; font-size:0.75rem;" title="Set Hari Ini">Hari Ini</button>');
             $todayBtn.on('click', function() { $dateInp.val(getTodayString()); });
             $todayBtn.on('click', function() { $dateInp.val(getTodayString()); });
Baris 620: Baris 634:
             $form.append($refArea);
             $form.append($refArea);


            // PERINGATAN CC0 & EDIT SUMMARY
             $form.append(`
             $form.append(`
                 <div class="md-cc0-notice">
                 <div class="md-cc0-notice">
Baris 632: Baris 645:
             `);
             `);


            // TOMBOL AKSI FORM
             var $actions = $('<div class="md-form-actions"></div>');
             var $actions = $('<div class="md-form-actions"></div>');
             var $btnSave = $('<button class="md-btn md-btn-save">Terbitkan <span class="md-spinner"></span></button>');
             var $btnSave = $('<button class="md-btn md-btn-save">Terbitkan <span class="md-spinner"></span></button>');
Baris 663: Baris 675:
             $form.append($actions);
             $form.append($actions);
             $container.append($form);
             $container.append($form);
             initTooltips(); // Inisialisasi tooltips
             initTooltips(); // Inisialisasi tooltips aman di dalam DOM baru


             $('#md-chk-person').on('change', function() {
             $('#md-chk-person').on('change', function() {
Baris 673: Baris 685:
             $btnCancel.on('click', function() { $container.empty(); });
             $btnCancel.on('click', function() { $container.empty(); });


            // SIMPAN DRAFT KE LOKAL
             $btnDraft.on('click', function() {
             $btnDraft.on('click', function() {
                 var socObj = {};
                 var socObj = {};
Baris 692: Baris 703:
                     ref: {
                     ref: {
                         type: $('#md-ref-type').val(), subType: $('#md-ref-subtype').val(),
                         type: $('#md-ref-type').val(), subType: $('#md-ref-subtype').val(),
                         url: $('#md-ref-url').val(), title: $('#md-ref-title').val().trim(),
                         url: ensureHttps($('#md-ref-url').val()), title: $('#md-ref-title').val().trim(),
                         site: $('#md-ref-site').val().trim(), author: $('#md-ref-author').val().trim(),
                         site: $('#md-ref-site').val().trim(), author: $('#md-ref-author').val().trim(),
                         publisher: $('#md-ref-publisher').val().trim(), date: $('#md-ref-date').val()
                         publisher: $('#md-ref-publisher').val().trim(), date: $('#md-ref-date').val()
Baris 701: Baris 712:
             });
             });


            // TERBITKAN
             $btnSave.on('click', function() {
             $btnSave.on('click', function() {
                 var newProp = $('#md-inp-prop').val().trim();
                 var newProp = $('#md-inp-prop').val().trim();