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 | * MIPPEDIA DATA - ADVANCED STRUCTURED DATA SYSTEM (V8.0 ULTIMATE) | ||
* | * Peningkatan: Sistem Modal Pop-up, Viewer Referensi, Tour Bantuan, | ||
* Toast | * Smart Lock Notification, Toast Alerts, Reorder Data, Animasi Mulus. | ||
* ==================================================================== | * ==================================================================== | ||
*/ | */ | ||
| Baris 20: | Baris 20: | ||
var api = new mw.Api(); | var api = new mw.Api(); | ||
// | // 1. DATA MASTER PLATFORM MEDIA SOSIAL & MUSIK | ||
var SOCIAL_PLATFORMS = { | var SOCIAL_PLATFORMS = { | ||
applemusic: { name: 'Apple Music', base: 'https://music.apple.com/artist/', color: '#fc3c44' }, | applemusic: { name: 'Apple Music', base: 'https://music.apple.com/artist/', color: '#fc3c44', icon: '<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm3.32 12.383c-.328.232-.82.418-1.32.418-1.238 0-1.922-.828-1.922-2.191v-4.14l4.312-.868v1.398l-2.91.586v2.805c0 .734.336 1.148.977 1.148.336 0 .617-.094.863-.258v1.102z"/></svg>' }, | ||
spotify: { name: 'Spotify', base: 'https://open.spotify.com/artist/', color: '#1db954' }, | spotify: { name: 'Spotify', base: 'https://open.spotify.com/artist/', color: '#1db954', icon: '<svg width="13" height="13" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm4.586 14.424c-.18.295-.563.387-.857.207-2.35-1.434-5.308-1.758-8.793-.963-.335.077-.67-.133-.746-.468-.077-.334.132-.67.467-.745 3.816-.87 7.092-.493 9.72 1.112.294.18.386.563.209.857zm1.213-2.71c-.226.368-.703.483-1.07.258-2.688-1.652-6.785-2.13-9.965-1.164-.413.125-.85-.108-.975-.522-.125-.413.108-.85.522-.975 3.633-1.102 8.157-.568 11.23 1.332.368.226.483.703.258 1.071zm.14-2.827c-3.225-1.915-8.54-2.092-11.614-1.16-.503.153-1.037-.133-1.19-.636-.152-.503.133-1.037.636-1.19 3.532-1.072 9.404-.865 13.115 1.338.45.267.598.85.33 1.3-.267.45-.85.598-1.3.33z"/></svg>' }, | ||
youtube: { name: 'YouTube', base: 'https://youtube.com/@', color: '#ff0000', icon: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M22.54 6.42a2.78 2.78 0 0 0-1.94-2C18.88 4 12 4 12 4s-6.88 0-8.6.46a2.78 2.78 0 0 0-1.94 2A29 29 0 0 0 1 11.75a29 29 0 0 0 .46 5.33A2.78 2.78 0 0 0 3.4 19c1.72.46 8.6.46 8.6.46s6.88 0 8.6-.46a2.78 2.78 0 0 0 1.94-2 29 29 0 0 0 .46-5.25 29 29 0 0 0-.46-5.33z"></path><polygon points="9.75 15.02 15.5 11.75 9.75 8.48 9.75 15.02"></polygon></svg>' }, | |||
instagram: { name: 'Instagram', base: 'https://instagram.com/', color: '#e1306c', icon: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="2" y="2" width="20" height="20" rx="5" ry="5"></rect><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"></path><line x1="17.5" y1="6.5" x2="17.51" y2="6.5"></line></svg>' }, | |||
twitter: { name: 'X / Twitter', base: 'https://x.com/', color: '#0f1419', icon: '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z"></path></svg>' } | |||
twitter: { name: 'X / Twitter', base: 'https://x.com/', color: '#0f1419' } | |||
}; | }; | ||
var PRESET_LABELS = [ | var PRESET_LABELS = ["Nama Lahir", "Tanggal Lahir", "Tempat Lahir", "Pekerjaan", "Kebangsaan", "Genre", "Situs Web Resmi", "Pasangan", "Orang Tua"]; | ||
var REF_TYPES = { | var REF_TYPES = { | ||
"Berita": ["Berita Online", "Berita Cetak", "Wawancara"], | "Berita": ["Berita Online", "Berita Cetak / Koran", "Wawancara Eksklusif"], | ||
"Website": ["Situs Resmi", "Portal Informasi", "Database Web"], | "Website": ["Situs Resmi", "Portal Informasi", "Database Web"], | ||
"Buku": ["Buku Cetak", "E-Book", "Jurnal Ilmiah"], | "Buku": ["Buku Cetak", "E-Book", "Bab Buku"], | ||
" | "Jurnal": ["Jurnal Ilmiah", "Paper Konferensi", "Tesis"], | ||
"Siaran Pers": ["Pernyataan Resmi", "Keterangan Manajemen"], | |||
"Video": ["YouTube Video", "Film Dokumenter"] | |||
}; | }; | ||
// | // IKON SISTEM | ||
var | 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 svgUnlock = '<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 9.9-1"></path></svg>'; | |||
var svgRef = '<svg class="md-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"></path><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"></path></svg>'; | |||
var svgCopy = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>'; | |||
var svgHelp = '<svg width="15" height="15" 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 svgExternal = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"></path><polyline points="15 3 21 3 21 9"></polyline><line x1="10" y1="14" x2="21" y2="3"></line></svg>'; | |||
var svgUp = '<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="18 15 12 9 6 15"></polyline></svg>'; | |||
var svgDown = '<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="6 9 12 15 18 9"></polyline></svg>'; | |||
// | // 5. HELPER UTILITY | ||
function formatIndonesianDate(dateStr) { | |||
if (!dateStr) return ''; | |||
var parts = dateStr.split('-'); | |||
if (parts.length !== 3) return dateStr; | |||
var months = ['Januari', 'Februari', 'Maret', 'April', 'Mei', 'Juni', 'Juli', 'Agustus', 'September', 'Oktober', 'November', 'Desember']; | |||
return parseInt(parts[2], 10) + ' ' + months[parseInt(parts[1], 10) - 1] + ' ' + parts[0]; | |||
} | |||
function extractDomain(urlStr) { | |||
try { return new URL(urlStr.startsWith('http') ? urlStr : 'https://' + urlStr).hostname.replace(/^www\./, ''); } catch(e) { return urlStr; } | |||
} | |||
function ensureHttps(url) { return (url && !url.match(/^https?:\/\//i)) ? 'https://' + url : url; } | |||
// TOAST NOTIFICATION | |||
function showToast(msg, type) { | |||
$('.md-toast').remove(); | |||
var bg = type === 'success' ? '#10b981' : (type === 'error' ? '#ef4444' : '#3b82f6'); | |||
var $toast = $('<div class="md-toast" style="position:fixed; bottom:20px; right:20px; background:'+bg+'; color:#fff; padding:12px 20px; border-radius:8px; box-shadow:0 4px 12px rgba(0,0,0,0.15); z-index:10001; font-weight:600; opacity:0; transform:translateY(20px); transition:all 0.3s;">' + msg + '</div>'); | |||
$('body').append($toast); | |||
setTimeout(() => { $toast.css({'opacity': '1', 'transform': 'translateY(0)'}); }, 10); | |||
setTimeout(() => { $toast.css({'opacity': '0', 'transform': 'translateY(20px)'}); setTimeout(()=> $toast.remove(), 300); }, 3000); | |||
setTimeout(() => { $ | |||
} | } | ||
// --- | // 6. STYLESHEET CSS V8.0 (MODALS, TOASTS, UI) | ||
var css = ` | |||
#mippedia-data-app { background: #fff; border-radius: 16px; box-shadow: 0 4px 24px rgba(0,0,0,0.06); padding: 20px; margin: 20px 0; border: 1px solid #e2e8f0; font-family: sans-serif; position: relative;} | |||
.md-header { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 12px; margin-bottom: 16px; border-bottom: 1px solid #f1f5f9; padding-bottom:14px; } | |||
.md-title { font-size: 1.25rem; font-weight: 800; color: #0f172a; margin: 0; display:flex; align-items:center; gap:8px;} | |||
.md-actions { display: flex; gap: 8px; flex-wrap:wrap; } | |||
.md-btn-icon { background: #f8fafc; border: 1px solid #cbd5e1; cursor: pointer; color: #4f46e5; padding: 8px 14px; border-radius: 8px; transition: 0.2s; display: inline-flex; align-items: center; gap: 6px; font-weight: 600; font-size:0.85rem;} | |||
.md-btn-icon:hover { background: #e0e7ff; } | |||
.md-btn-lock { color: #dc2626; background: #fef2f2; border-color: #fca5a5; } | |||
.md-row-display { display: flex; justify-content: space-between; align-items: flex-start; padding: 14px; border-bottom: 1px solid #f1f5f9; border-radius: 12px; gap: 12px; background: #fff; transition:0.3s; margin-bottom:8px;} | |||
.md-row-display:hover { background: #f8fafc; box-shadow: inset 4px 0 0 #4f46e5; } | |||
.md-data-group { display: flex; flex-direction: column; gap: 4px; flex: 1; } | |||
.md-data-label { font-size: 0.72rem; color: #64748b; font-weight: 800; text-transform: uppercase; } | |||
.md-data-value { font-size: 1.05rem; color: #0f172a; font-weight: 600; } | |||
.md-btn-copy { background: none; border: none; color: #94a3b8; cursor: pointer; display:inline-flex; transition:0.2s; margin-left:8px;} | |||
.md-btn-copy:hover { color:#4f46e5; } | |||
/* Reference Badge (Mini) */ | |||
.md-ref-badge-trigger { display:inline-flex; align-items:center; gap:4px; font-size:0.75rem; background:#eff6ff; color:#2563eb; padding:2px 8px; border-radius:12px; cursor:pointer; font-weight:700; border:1px solid #bfdbfe; transition:0.2s; margin-top:4px; width:fit-content;} | |||
.md-ref-badge-trigger:hover { background:#dbeafe; transform:scale(1.05); } | |||
/* MODAL SYSTEM (V8.0) */ | |||
.md-modal-overlay { position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(15, 23, 42, 0.6); backdrop-filter:blur(4px); z-index:9999; display:flex; justify-content:center; align-items:center; opacity:0; animation: fadeIn 0.2s forwards; padding:16px;} | |||
.md-modal-box { background:#fff; border-radius:16px; width:100%; max-width:600px; max-height:90vh; overflow-y:auto; box-shadow:0 20px 25px -5px rgba(0,0,0,0.1); transform:translateY(20px); animation: slideUp 0.3s forwards; display:flex; flex-direction:column;} | |||
.md-modal-header { padding:16px 20px; border-bottom:1px solid #e2e8f0; display:flex; justify-content:space-between; align-items:center; background:#f8fafc; border-radius:16px 16px 0 0;} | |||
.md-modal-header h3 { margin:0; font-size:1.1rem; font-weight:800; color:#0f172a;} | |||
.md-btn-close { background:none; border:none; font-size:1.5rem; cursor:pointer; color:#64748b; line-height:1;} | |||
.md-modal-body { padding:20px; flex:1;} | |||
/* Tour / Help Tooltip */ | |||
.md-tour-overlay { position:fixed; z-index:10000; background:rgba(0,0,0,0.8); color:white; padding:16px; border-radius:12px; max-width:250px; text-align:center; box-shadow:0 10px 15px rgba(0,0,0,0.3); animation: bounceIn 0.5s;} | |||
.md-tour-overlay::after { content:''; position:absolute; border-width:10px; border-style:solid; } | |||
.md-tour-arrow-up::after { bottom:100%; left:50%; transform:translateX(-50%); border-color:transparent transparent rgba(0,0,0,0.8) transparent; } | |||
/* Form Elements inside Modal */ | |||
.md-form-group { margin-bottom: 14px; } | |||
.md-form-group label { display: block; font-size: 0.85rem; font-weight: 700; margin-bottom: 6px; color: #334155; } | |||
.md-input, .md-select { width: 100%; padding: 10px 12px; border: 1px solid #cbd5e1; border-radius: 8px; box-sizing: border-box; font-size: 0.9rem; transition: 0.2s; background:#f8fafc;} | |||
.md-input:focus { border-color: #4f46e5; outline: none; background:#fff; box-shadow:0 0 0 3px rgba(79, 70, 229, 0.1); } | |||
.md-btn { padding: 10px 18px; border: none; border-radius: 8px; font-weight: 700; cursor: pointer; color: white; display: inline-flex; align-items: center; justify-content:center; gap: 6px; transition: 0.2s; } | |||
.md-btn-save { background: linear-gradient(135deg, #4f46e5, #4338ca); flex:1;} | |||
.md-btn-save:hover { filter:brightness(1.1); transform:translateY(-1px);} | |||
.md-btn-cancel { background: #64748b; } | |||
/* Animations */ | |||
@keyframes fadeIn { to { opacity:1; } } | |||
@keyframes slideUp { to { transform:translateY(0); } } | |||
@keyframes bounceIn { 0% {transform:scale(0.8); opacity:0;} 50% {transform:scale(1.05);} 100% {transform:scale(1); opacity:1;} } | |||
.md-spinner { display: none; width: 14px; height: 14px; border: 2px solid rgba(255,255,255,0.4); border-radius: 50%; border-top-color: #fff; animation: md-spin 0.8s linear infinite; } | |||
.is-loading .md-spinner { display: inline-block; } | |||
@keyframes md-spin { to { transform: rotate(360deg); } } | |||
.md-preset-pill { background: #f1f5f9; padding: 4px 10px; border-radius: 12px; font-size: 0.75rem; font-weight: 600; cursor: pointer; border:1px solid #cbd5e1; display:inline-block; margin:2px;} | |||
.md-preset-pill:hover { background: #e0e7ff; border-color:#a5b4fc;} | |||
.md-native-render { display: none; } | |||
`; | |||
$('<style>').text(css).appendTo('head'); | |||
// | // 7. PARSER DATA | ||
function loadData() { | function loadData() { | ||
return api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', rvslots: 'main', formatversion: 2 }) | return api.get({ action: 'query', prop: 'revisions', titles: pageName, rvprop: 'content', rvslots: 'main', formatversion: 2 }).then(function(data) { | ||
var content = data.query.pages[0].revisions[0].slots.main.content; | var content = data.query.pages[0].revisions[0].slots.main.content; | ||
var match = content.match(/<!-- MIPPEDIA_DATA_START \| (.*?) -->/); | var match = content.match(/<!-- MIPPEDIA_DATA_START \| (.*?) -->/); | ||
| Baris 266: | Baris 144: | ||
} | } | ||
function saveData(actionType, | function buildWikitextVal(item) { | ||
if($ | var val = item.val; | ||
if (item.linkTarget) return "[[" + item.linkTarget.trim() + "|" + val + "]]"; | |||
if (item.link) return "[[" + val + "]]"; | |||
return val; | |||
} | |||
function buildWikitextRef(refObj) { | |||
if (!refObj) return ""; | |||
var title = refObj.title || "Rujukan"; | |||
return refObj.url ? "[" + ensureHttps(refObj.url) + " " + title + "]" : "'''" + title + "'''"; | |||
} | |||
function saveData(actionType, $btnEl, originalContent) { | |||
if ($btnEl) $btnEl.addClass('is-loading').prop('disabled', true); | |||
var jsonStr = JSON.stringify({ locked: isLocked, items: dataItems }); | var jsonStr = JSON.stringify({ locked: isLocked, items: dataItems }); | ||
var wt = "<!-- MIPPEDIA_DATA_START | " + jsonStr + " -->\n<div class=\"md-native-render\">\n{| class=\"wikitable\"\n! Data !! Nilai\n"; | var wt = "<!-- MIPPEDIA_DATA_START | " + jsonStr + " -->\n<div class=\"md-native-render\">\n{| class=\"wikitable\"\n! Data !! Nilai !! Referensi\n"; | ||
dataItems.forEach(function(item) { wt += "|-\n| " + item.prop + " || " + item. | dataItems.forEach(function(item) { wt += "|-\n| " + item.prop + " || " + buildWikitextVal(item) + " || " + buildWikitextRef(item.ref) + "\n"; }); | ||
wt += "|}\n</div>\n<!-- MIPPEDIA_DATA_END -->"; | wt += "|}\n</div>\n<!-- MIPPEDIA_DATA_END -->"; | ||
var newContent | var newContent; | ||
var regex = /<!-- MIPPEDIA_DATA_START \| .*? -->[\s\S]*?<!-- MIPPEDIA_DATA_END -->/; | var regex = /<!-- MIPPEDIA_DATA_START \| .*? -->[\s\S]*?<!-- MIPPEDIA_DATA_END -->/; | ||
if (regex.test( | if (regex.test(originalContent)) newContent = originalContent.replace(regex, wt); | ||
else newContent = | else newContent = originalContent.trim() + "\n\n" + wt; | ||
api.postWithToken('csrf', { action: 'edit', title: pageName, text: newContent, summary: 'Mippedia V8: ' + actionType }) | api.postWithToken('csrf', { action: 'edit', title: pageName, text: newContent, summary: 'Mippedia Data V8: ' + actionType, minor: false }).done(function() { | ||
showToast('Data berhasil disimpan!', 'success'); | |||
setTimeout(() => location.reload(), 800); | |||
}).fail(function(err) { | |||
if ($btnEl) $btnEl.removeClass('is-loading').prop('disabled', false); | |||
showToast("Gagal menyimpan: " + err, 'error'); | |||
}); | |||
} | } | ||
// - | // ========================================== | ||
function | // SISTEM MODAL POP-UP & PANDUAN | ||
var | // ========================================== | ||
var | function openModal(title, contentHtml, onInit) { | ||
var | $('.md-modal-overlay').remove(); // Clear existing | ||
var $overlay = $('<div class="md-modal-overlay"></div>'); | |||
var $box = $('<div class="md-modal-box"></div>'); | |||
var $header = $('<div class="md-modal-header"><h3>'+title+'</h3><button class="md-btn-close">×</button></div>'); | |||
var $body = $('<div class="md-modal-body">'+contentHtml+'</div>'); | |||
$box.append($header).append($body); | |||
$overlay.append($box); | |||
$('body').append($overlay); | |||
$header.find('.md-btn-close').on('click', function() { $overlay.remove(); }); | |||
$overlay.on('click', function(e) { if(e.target === this) $overlay.remove(); }); | |||
$(document).on('keydown.mdmodal', function(e) { if(e.key === "Escape") { $overlay.remove(); $(document).off('keydown.mdmodal'); } }); | |||
if(onInit) onInit($body, $overlay); | |||
} | |||
var | // Pop-up Notifikasi Gembok Admin | ||
function showLockExplanation() { | |||
var html = '<div style="text-align:center; padding:10px;">' + | |||
'<div style="font-size:3rem; color:#ef4444; margin-bottom:10px;">' + svgLock + '</div>' + | |||
'<h2 style="margin:0 0 10px 0; color:#0f172a;">Data Ini Dikunci</h2>' + | |||
'<p style="color:#475569; font-size:0.95rem; line-height:1.5;">Halaman struktur data ini sedang dikunci oleh <b>Pengurus Mippedia</b>. Pengguna biasa atau anonim tidak dapat menambah atau mengedit data ini untuk mencegah vandalisme atau perubahan informasi penting.</p>' + | |||
'<button id="md-close-lock" class="md-btn md-btn-cancel" style="margin-top:20px; width:100%;">Saya Mengerti</button>' + | |||
'</div>'; | |||
openModal('Informasi Keamanan', html, function($body, $overlay) { | |||
$body.find('#md-close-lock').on('click', () => $overlay.remove()); | |||
}); | |||
} | |||
// Pop-up Penampil Referensi (Viewer Mode) | |||
function showRefModal(ref) { | |||
var html = '<div style="display:flex; flex-direction:column; gap:12px; font-size:0.95rem;">'; | |||
html += '<div><span style="color:#64748b; font-weight:700; font-size:0.8rem; text-transform:uppercase;">Judul Referensi</span><div style="font-size:1.1rem; font-weight:800; color:#0f172a;">' + (ref.title || 'Tanpa Judul') + '</div></div>'; | |||
var grid = '<div style="display:grid; grid-template-columns:1fr 1fr; gap:10px; background:#f8fafc; padding:16px; border-radius:12px; border:1px solid #e2e8f0;">'; | |||
if(ref.author) grid += '<div><b style="color:#64748b; font-size:0.8rem;">Penulis:</b><br>' + ref.author + '</div>'; | |||
if(ref.site) grid += '<div><b style="color:#64748b; font-size:0.8rem;">Situs:</b><br>' + ref.site + '</div>'; | |||
if(ref.publisher) grid += '<div><b style="color:#64748b; font-size:0.8rem;">Penerbit:</b><br>' + ref.publisher + '</div>'; | |||
if(ref.date) grid += '<div><b style="color:#64748b; font-size:0.8rem;">Tanggal Akses:</b><br>' + formatIndonesianDate(ref.date) + '</div>'; | |||
if(ref.type) grid += '<div style="grid-column:span 2;"><b style="color:#64748b; font-size:0.8rem;">Kategori:</b><br><span style="background:#dbeafe; color:#1e40af; padding:4px 10px; border-radius:12px; font-size:0.8rem; font-weight:700;">' + ref.type + (ref.subType ? ' - ' + ref.subType : '') + '</span></div>'; | |||
grid += '</div>'; | |||
html += grid; | |||
if (ref.url) { | |||
html += '<a href="'+ensureHttps(ref.url)+'" target="_blank" class="md-btn md-btn-save" style="margin-top:10px; text-decoration:none; display:flex; justify-content:center;">' + svgExternal + ' Kunjungi Website Referensi</a>'; | |||
} | |||
html += '</div>'; | |||
openModal('Detail Referensi Terstruktur', html); | |||
} | |||
// Bantuan Tooltip Otomatis | |||
function startHelpTour() { | |||
var $tour = $('<div class="md-tour-overlay md-tour-arrow-up"><b>Panduan Tambah Data!</b><br><span style="font-size:0.85rem;">Klik di sini untuk menambah data baru. Form akan muncul sebagai pop-up yang praktis!</span><br><button class="md-btn md-btn-cancel" style="padding:4px 8px; margin-top:8px; font-size:0.75rem;" onclick="$(\'.md-tour-overlay\').remove();">Tutup</button></div>'); | |||
var offset = $('#btn-add-data').offset(); | |||
if(offset) { | |||
$tour.css({ top: offset.top + 45 + 'px', left: offset.left - 50 + 'px' }); | |||
$('body').append($tour); | |||
setTimeout(() => $tour.remove(), 10000); | |||
} | |||
$ | |||
} | |||
} | } | ||
// | // 8. BUILDER UI UTAMA | ||
function buildUI(rawContent) { | function buildUI(rawContent) { | ||
var $app = $('<div id="mippedia-data-app"></div>'); | var $app = $('<div id="mippedia-data-app"></div>'); | ||
// | // HEADER | ||
var $header = $('<div class="md-header"></div>'); | var $header = $('<div class="md-header"></div>'); | ||
$ | var $titleContainer = $('<h3 class="md-title">' + svgEdit + ' Data Terstruktur</h3>'); | ||
var $actions = $('<div class="md-actions"></div>'); | |||
var $ | |||
// Tombol Panduan Bantuan | |||
var $helpBtn = $('<button class="md-btn-icon" title="Panduan Fitur">' + svgHelp + ' Bantuan</button>'); | |||
if(isSysop) { | $helpBtn.on('click', startHelpTour); | ||
$actions.append($helpBtn); | |||
var $ | |||
$ | if (isSysop) { | ||
var $lockBtn = $('<button class="md-btn-icon ' + (isLocked ? 'md-btn-lock' : '') + '">' + (isLocked ? svgLock+' Terkunci' : svgUnlock+' Terbuka') + '</button>'); | |||
$lockBtn.on('click', function() { isLocked = !isLocked; saveData('Ubah status gembok', $(this), rawContent); }); | |||
$actions.append($lockBtn); | |||
$actions.append($ | |||
} else if (isLocked) { | } else if (isLocked) { | ||
$actions.append('<button class="md-btn-icon md-btn-lock" id="btn-lock-info">' + svgLock + ' Terkunci</button>'); | |||
$ | setTimeout(()=> $('#btn-lock-info').on('click', showLockExplanation), 100); | ||
} | } | ||
var $addBtn = $('<button class="md-btn-icon" id="btn-add-data" style="background:#4f46e5; color:#fff; border-color:#4338ca;">' + svgEdit + ' Tambah Data</button>'); | |||
$addBtn.on('click', function() { | |||
if(!isSysop && isLocked) { showLockExplanation(); return; } | |||
showFormModal(-1, rawContent); | |||
}); | |||
$actions.append($addBtn); | |||
if( | |||
$header.append($actions); | $header.append($titleContainer).append($actions); | ||
$app.append($header); | $app.append($header); | ||
// | // DAFTAR DATA | ||
var $list = $('<div></div>'); | var $list = $('<div class="md-list"></div>'); | ||
if(dataItems.length === 0) { | if (dataItems.length === 0) { | ||
$list.append('<div style="text-align:center; padding: | $list.append('<div style="text-align:center; padding: 25px 10px; background:#f8fafc; border-radius:10px; color:#64748b;">Belum ada data. Klik Tambah Data.</div>'); | ||
} else { | } else { | ||
dataItems.forEach(function(item, | dataItems.forEach(function(item, index) { | ||
var $row = $('<div class="md-row-display"></div>'); | var $row = $('<div class="md-row-display"></div>'); | ||
$info.append( | // REORDER BUTTONS (Naik / Turun) | ||
$ | if (!isLocked || isSysop) { | ||
var $orderCol = $('<div style="display:flex; flex-direction:column; gap:4px; margin-right:12px;"></div>'); | |||
var $up = $('<button style="background:none; border:none; color:#cbd5e1; cursor:pointer;" title="Naikkan Data">'+svgUp+'</button>'); | |||
var $down = $('<button style="background:none; border:none; color:#cbd5e1; cursor:pointer;" title="Turunkan Data">'+svgDown+'</button>'); | |||
if(index > 0) { | |||
$up.on('click', function() { | |||
var temp = dataItems[index-1]; | |||
dataItems[index-1] = dataItems[index]; | |||
dataItems[index] = temp; | |||
saveData('Reorder baris data', null, rawContent); | |||
}); | |||
$up.css('color', '#64748b').hover(function(){$(this).css('color','#4f46e5')}, function(){$(this).css('color','#64748b')}); | |||
} else { $up.css('visibility', 'hidden'); } | |||
if(index < dataItems.length - 1) { | |||
$down.on('click', function() { | |||
var temp = dataItems[index+1]; | |||
dataItems[index+1] = dataItems[index]; | |||
dataItems[index] = temp; | |||
saveData('Reorder baris data', null, rawContent); | |||
}); | |||
$down.css('color', '#64748b').hover(function(){$(this).css('color','#4f46e5')}, function(){$(this).css('color','#64748b')}); | |||
} else { $down.css('visibility', 'hidden'); } | |||
$orderCol.append($up).append($down); | |||
$row.append($orderCol); | |||
} | |||
var $info = $('<div class="md-data-group"></div>'); | |||
$info.append('<span class="md-data-label">' + item.prop + '</span>'); | |||
var displayValHtml = item.val; | |||
if(item.val.match(/^https?:\/\//i)) displayValHtml = '<a href="'+item.val+'" target="_blank" style="color:#2563eb;">'+extractDomain(item.val)+'</a>'; | |||
var $valWrap = $('<div style="display:flex; align-items:center;"></div>'); | |||
$valWrap.append('<span class="md-data-value">' + displayValHtml + '</span>'); | |||
/ | var $copyBtn = $('<button class="md-btn-copy" title="Salin">' + svgCopy + '</button>'); | ||
if(item.ref && (item.ref.url || item.ref.title)) { | $copyBtn.on('click', function() { | ||
var $ | navigator.clipboard.writeText(item.val); | ||
$ | showToast('Nilai data tersalin!', 'success'); | ||
$info.append($ | }); | ||
$valWrap.append($copyBtn); | |||
$info.append($valWrap); | |||
// MINI REFERENCE BADGE (Click to open Popup) | |||
if (item.ref && (item.ref.url || item.ref.title)) { | |||
var $refBadge = $('<div class="md-ref-badge-trigger" title="Klik untuk lihat detail referensi">' + svgRef + ' [1] Rujukan</div>'); | |||
$refBadge.on('click', function() { showRefModal(item.ref); }); | |||
$info.append($refBadge); | |||
} | } | ||
$row.append($info); | $row.append($info); | ||
if(!isLocked || isSysop) { | if (!isLocked || isSysop) { | ||
var $editBtn = $( | var $editBtn = $('<button class="md-btn-icon" style="padding:6px 10px; font-size:0.75rem;">' + svgEdit + '</button>'); | ||
$editBtn.on('click', function() { showFormModal( | $editBtn.on('click', function() { showFormModal(index, rawContent); }); | ||
$row.append($editBtn); | $row.append($editBtn); | ||
} | } | ||
| Baris 451: | Baris 350: | ||
}); | }); | ||
} | } | ||
$app.append($list); | |||
$('.md-native-render').hide(); | |||
var $infobox = $('.infobox').first(); | |||
if ($infobox.length) $app.insertAfter($infobox); else $('#mw-content-text .mw-parser-output').append($app); | |||
} | |||
// 9. FORM EDIT POP-UP MODAL | |||
function showFormModal(index, rawContent) { | |||
var isNew = index === -1; | |||
var cData = isNew ? { prop:'', val:'', ref:{} } : dataItems[index]; | |||
var cRef = cData.ref || {}; | |||
var html = ''; | |||
html += '<div class="md-form-group"><label>Label Data</label><input type="text" id="md-inp-prop" class="md-input" value="'+(cData.prop||'')+'"></div>'; | |||
$ | // Presets | ||
html += '<div style="margin-bottom:14px;">'; | |||
PRESET_LABELS.forEach(l => html += '<span class="md-preset-pill" onclick="$(\'#md-inp-prop\').val(\''+l+'\'); $(\'#md-inp-val\').focus();">'+l+'</span>'); | |||
html += '</div>'; | |||
html += '<div class="md-form-group"><label>Nilai Data</label><input type="text" id="md-inp-val" class="md-input" value="'+(cData.val||'')+'"></div>'; | |||
var $ | // Referensi Area | ||
html += '<div style="background:#f8fafc; padding:14px; border-radius:12px; border:1px solid #e2e8f0; margin-top:20px;">'; | |||
html += '<h4 style="margin:0 0 10px 0; font-size:0.9rem; color:#4f46e5; display:flex; align-items:center; gap:6px;">'+svgRef+' Tambah Rujukan (Opsional)</h4>'; | |||
html += '<div style="display:grid; grid-template-columns:1fr 1fr; gap:10px;">'; | |||
html += '<input type="text" id="md-ref-title" class="md-input" style="grid-column:span 2;" placeholder="Judul Referensi" value="'+(cRef.title||'')+'">'; | |||
html += '<input type="url" id="md-ref-url" class="md-input" style="grid-column:span 2;" placeholder="URL / Link (https://...)" value="'+(cRef.url||'')+'">'; | |||
html += '<input type="text" id="md-ref-author" class="md-input" placeholder="Penulis" value="'+(cRef.author||'')+'">'; | |||
html += '<input type="text" id="md-ref-publisher" class="md-input" placeholder="Penerbit / Web" value="'+(cRef.publisher||'')+'">'; | |||
html += '</div></div>'; | |||
html += '<div style="display:flex; gap:10px; margin-top:20px; flex-wrap:wrap;">'; | |||
html += '<button id="md-modal-save" class="md-btn md-btn-save">Simpan Data <span class="md-spinner"></span></button>'; | |||
if(!isNew) html += '<button id="md-modal-del" class="md-btn" style="background:#ef4444;">Hapus</button>'; | |||
html += '</div>'; | |||
openModal(isNew ? 'Tambah Data Baru' : 'Edit Data', html, function($body, $overlay) { | |||
// Auto Capitalize Event | |||
$body.find('#md-inp-prop').on('input', function() { | |||
var val = $(this).val(); | |||
if(val.length > 0) $(this).val(val.charAt(0).toUpperCase() + val.slice(1)); | |||
}); | |||
$body.find('#md-modal-save').on('click', function() { | |||
var nProp = $('#md-inp-prop').val().trim(); | |||
var nVal = $('#md-inp-val').val().trim(); | |||
if(!nProp || !nVal) { showToast('Label dan Nilai harus diisi!', 'error'); return; } | |||
var rTitle = $('#md-ref-title').val().trim(); | |||
var rUrl = $('#md-ref-url').val().trim(); | |||
var refObj = null; | |||
if(rTitle || rUrl) { | |||
refObj = { | |||
title: rTitle, url: ensureHttps(rUrl), | |||
author: $('#md-ref-author').val().trim(), publisher: $('#md-ref-publisher').val().trim() | |||
}; | |||
} | |||
var newItem = { prop: nProp, val: nVal, ref: refObj }; | |||
if(isNew) dataItems.push(newItem); else dataItems[index] = newItem; | |||
saveData(isNew ? 'Tambah data popup' : 'Edit data popup', $(this), rawContent); | |||
}); | |||
if(!isNew) { | |||
$body.find('#md-modal-del').on('click', function() { | |||
if(confirm('Yakin hapus data ini?')) { | |||
dataItems.splice(index, 1); | |||
saveData('Hapus data popup', $(this), rawContent); | |||
} | |||
}); | |||
} | |||
}); | |||
} | } | ||
loadData().then(function(rawContent) { buildUI(rawContent); }); | loadData().then(function(rawContent) { buildUI(rawContent); }); | ||
}); | }); | ||