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. | * MIPPEDIA DATA - ADVANCED STRUCTURED DATA SYSTEM (V8.5 SECURE ULTIMATE) | ||
* Fitur: ALL ORIGINAL V7 FEATURES | * Fitur: ALL ORIGINAL V7/V8 FEATURES KEPT + Responsive Verified Ticks, | ||
* | * Smart Tooltip Position, Advanced XSS Security, Detailed Ref Instructions. | ||
* ==================================================================== | * ==================================================================== | ||
*/ | */ | ||
| Baris 38: | Baris 38: | ||
}; | }; | ||
// 2. PRESET & MASTER RUJUKAN | // 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: | ||
}; | }; | ||
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 | // 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 { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[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 | // 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; } | ||
.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; } | ||
.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 | .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: | .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; } | ||
.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; } | ||
.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: | .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(); | ||
// | var ttWidth = $tt.outerWidth(); | ||
$tt.css({ top: offset.top - $tt.outerHeight() - 10, left: | 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 | // 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); | 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); | ||
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: | ||
} | } | ||
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 ? ' • ' + 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 ? ' • ' + 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; | ||
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>'); | ||
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); | ||
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); | ||
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); | ||
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=" | // 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); | ||
$form.append(` | $form.append(` | ||
<div class="md-cc0-notice"> | <div class="md-cc0-notice"> | ||
| Baris 632: | Baris 645: | ||
`); | `); | ||
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(); }); | ||
$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: | ||
}); | }); | ||
$btnSave.on('click', function() { | $btnSave.on('click', function() { | ||
var newProp = $('#md-inp-prop').val().trim(); | var newProp = $('#md-inp-prop').val().trim(); | ||