MediaWiki:Common.js: Perbedaan antara revisi
Tidak ada ringkasan suntingan Tanda: Dikembalikan Suntingan perangkat seluler Suntingan peramban seluler |
Tidak ada ringkasan suntingan Tanda: Dikembalikan Suntingan perangkat seluler Suntingan peramban seluler |
||
| Baris 1: | Baris 1: | ||
/** | /** | ||
* ==================================================================== | * ==================================================================== | ||
* MIPPEDIA DATA - ADVANCED STRUCTURED DATA SYSTEM ( | * MIPPEDIA DATA - ADVANCED STRUCTURED DATA SYSTEM (V9.0 PRO MOBILE) | ||
* Fitur: | * Fitur: No-Emoji, Mobile Responsive, 15 Auto Features, Clean UI. | ||
* ==================================================================== | * ==================================================================== | ||
*/ | */ | ||
| Baris 19: | Baris 18: | ||
var isLocked = false; | var isLocked = false; | ||
var isDarkMode = false; | var isDarkMode = false; | ||
var isReadOnly = false; | |||
var api = new mw.Api(); | var api = new mw.Api(); | ||
// 1. DATA MASTER PLATFORM MEDIA SOSIAL & MUSIK | // 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', 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>' }, | 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>' }, | ||
| Baris 54: | Baris 54: | ||
}; | }; | ||
// 2. IKON SISTEM FAST SVG | // 2. IKON SISTEM FAST SVG (PURE, NO EMOJIS) | ||
var svgDatabase = '<svg class="md-icon" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"></ellipse><path d="M21 12c0 1.66-4 3-9 3s-9-1.34-9-3"></path><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"></path></svg>'; | |||
var svgEdit = '<svg class="md-icon" width="14" height="14" 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="14" height="14" 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="14" height="14" 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="14" height="14" 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="14" height="14" 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 svgUnlock = '<svg class="md-icon" width="14" height="14" 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=" | var svgRef = '<svg class="md-icon" width="13" height="13" 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 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 svgClone = '<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><line x1="15.5" y1="12" x2="15.5" y2="18"></line><line x1="12.5" y1="15" x2="18.5" y2="15"></line></svg>'; | |||
var svgSearch = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>'; | var svgSearch = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>'; | ||
var svgCode = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg>'; | var svgCode = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"></polyline><polyline points="8 6 2 12 8 18"></polyline></svg>'; | ||
var | var svgCsv = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"></path><polyline points="14 2 14 8 20 8"></polyline><line x1="8" y1="13" x2="16" y2="13"></line><line x1="8" y1="17" x2="16" y2="17"></line></svg>'; | ||
var svgMoon = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>'; | var svgMoon = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>'; | ||
var svgWikitext = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7V4h16v3M9 20h6M12 4v16"/></svg>'; | var svgWikitext = '<svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M4 7V4h16v3M9 20h6M12 4v16"/></svg>'; | ||
var svgView = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path><circle cx="12" cy="12" r="3"></circle></svg>'; | |||
// 3. HELPER & AUTOMATION UTILITIES | // 3. HELPER & NEW AUTOMATION UTILITIES | ||
function formatIndonesianDate(dateStr) { | function formatIndonesianDate(dateStr) { | ||
if (!dateStr) return ''; | if (!dateStr) return ''; | ||
| Baris 88: | Baris 90: | ||
return urlStr.replace(/^https?:\/\/(www\.)?/, '').split('/')[0]; | return urlStr.replace(/^https?:\/\/(www\.)?/, '').split('/')[0]; | ||
} | } | ||
} | |||
// FITUR 13: Auto Reference Cleaner (Bersihkan URL Parameter Tracking) | |||
function cleanReferenceUrl(urlStr) { | |||
if (!urlStr) return ''; | |||
try { | |||
if (!urlStr.match(/^https?:\/\//i)) urlStr = 'https://' + urlStr; | |||
var urlObj = new URL(urlStr); | |||
var paramsToStrip = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid']; | |||
paramsToStrip.forEach(p => urlObj.searchParams.delete(p)); | |||
return urlObj.toString(); | |||
} catch(e) { return urlStr; } | |||
} | } | ||
| Baris 93: | Baris 107: | ||
if (!urlStr) return ''; | if (!urlStr) return ''; | ||
urlStr = urlStr.trim(); | urlStr = urlStr.trim(); | ||
// FITUR 8: Auto Protocol Inserter | |||
if (urlStr && !urlStr.match(/^https?:\/\//i)) return 'https://' + urlStr; | if (urlStr && !urlStr.match(/^https?:\/\//i)) return 'https://' + urlStr; | ||
return urlStr; | return cleanReferenceUrl(urlStr); | ||
} | } | ||
| Baris 105: | Baris 120: | ||
} | } | ||
// FORMAT: NO EMOJIS. Professional Pure Text Badges | |||
function detectDataTypeBadge(item) { | function detectDataTypeBadge(item) { | ||
var val = item.val || ''; | var val = item.val || ''; | ||
if (val.match(/^\d{4}-\d{2}-\d{2}$/)) return '<span class="md-type-tag | // FITUR 12: Smart Date Validator RegExp | ||
if (val.match(/^https?:\/\//i)) return '<span class="md-type-tag | if (val.match(/^\d{4}-\d{2}-\d{2}$/)) return '<span class="md-type-tag md-tag-date">Date</span>'; | ||
if (item.linkTarget) return '<span class="md-type-tag | if (val.match(/^https?:\/\//i)) return '<span class="md-type-tag md-tag-link">External Link</span>'; | ||
if (item.link) return '<span class="md-type-tag | if (item.linkTarget) return '<span class="md-type-tag md-tag-piped">Piped Link</span>'; | ||
return '<span class="md-type-tag | if (item.link) return '<span class="md-type-tag md-tag-wiki">Wikilink</span>'; | ||
return '<span class="md-type-tag md-tag-text">Text</span>'; | |||
} | |||
// FITUR 10: Data Type Demographics | |||
function calculateDataStats() { | |||
var stats = { date: 0, link: 0, wiki: 0, text: 0 }; | |||
dataItems.forEach(function(item) { | |||
var val = item.val || ''; | |||
if (val.match(/^\d{4}-\d{2}-\d{2}$/)) stats.date++; | |||
else if (val.match(/^https?:\/\//i)) stats.link++; | |||
else if (item.link || item.linkTarget) stats.wiki++; | |||
else stats.text++; | |||
}); | |||
return stats; | |||
} | } | ||
| Baris 127: | Baris 157: | ||
} | } | ||
// 4. STYLESHEET | // FITUR 4: CSV Export Logic | ||
function exportToCSV() { | |||
var headers = ["Label Data", "Nilai Data", "Tipe Link", "Target Piped", "URL Referensi", "Judul Referensi", "Situs Referensi"]; | |||
var rows = [headers]; | |||
dataItems.forEach(function(item) { | |||
var ref = item.ref || {}; | |||
var linkType = item.linkSplit ? "Split" : (item.link ? "Standard" : "None"); | |||
var row = [ | |||
'"' + (item.prop || '').replace(/"/g, '""') + '"', | |||
'"' + (item.val || '').replace(/"/g, '""') + '"', | |||
'"' + linkType + '"', | |||
'"' + (item.linkTarget || '').replace(/"/g, '""') + '"', | |||
'"' + (ref.url || '').replace(/"/g, '""') + '"', | |||
'"' + (ref.title || '').replace(/"/g, '""') + '"', | |||
'"' + (ref.site || '').replace(/"/g, '""') + '"' | |||
]; | |||
rows.push(row.join(",")); | |||
}); | |||
var csvContent = "data:text/csv;charset=utf-8," + rows.join("\n"); | |||
var encodedUri = encodeURI(csvContent); | |||
var link = document.createElement("a"); | |||
link.setAttribute("href", encodedUri); | |||
link.setAttribute("download", "Mippedia_Data_" + pageName + ".csv"); | |||
document.body.appendChild(link); | |||
link.click(); | |||
document.body.removeChild(link); | |||
} | |||
// 4. STYLESHEET V9.0 (MOBILE OPTIMIZED, NO EMOJIS, HIGH-PERFORMANCE ANIMATIONS) | |||
var css = ` | var css = ` | ||
#mippedia-data-app { | #mippedia-data-app { | ||
background: #ffffff; border-radius: | background: #ffffff; border-radius: 12px; | ||
padding: | padding: 24px; margin: 24px 0; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | ||
border: 1px solid #e2e8f0; | border: 1px solid #e2e8f0; width: 100%; box-sizing: border-box; | ||
transition: background 0.3s cubic-bezier(0.4, 0, 0.2, 1), border 0.3s; | |||
overflow: hidden; | |||
} | } | ||
#mippedia-data-app * { box-sizing: border-box; } | |||
#mippedia-data-app.md-dark { background: #0f172a; border-color: #334155; color: #f8fafc; } | #mippedia-data-app.md-dark { background: #0f172a; border-color: #334155; color: #f8fafc; } | ||
#mippedia-data-app.md-dark .md-title { color: #f8fafc; } | #mippedia-data-app.md-dark .md-title { color: #f8fafc; } | ||
#mippedia-data-app.md-dark .md-row-display { background: #1e293b; border-color: #334155; } | #mippedia-data-app.md-dark .md-row-display { background: #1e293b; border-color: #334155; } | ||
#mippedia-data-app.md-dark .md-data-value { color: #f1f5f9; } | #mippedia-data-app.md-dark .md-data-value { color: #f1f5f9; } | ||
#mippedia-data-app.md-dark .md-ref-card { background: # | #mippedia-data-app.md-dark .md-ref-card { background: #0f172a; border-color: #334155; } | ||
#mippedia-data-app.md-dark .md-form-box { background: #1e293b; border-color: #334155; } | #mippedia-data-app.md-dark .md-form-box { background: #1e293b; border-color: #334155; } | ||
#mippedia-data-app.md-dark .md-input, #mippedia-data-app.md-dark .md-select, #mippedia-data-app.md-dark .md-textarea { background: #0f172a; border-color: #475569; color: #fff; } | #mippedia-data-app.md-dark .md-input, #mippedia-data-app.md-dark .md-select, #mippedia-data-app.md-dark .md-textarea { background: #0f172a; border-color: #475569; color: #fff; } | ||
.md-header { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: | .md-header { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: center; gap: 16px; margin-bottom: 20px; padding-bottom: 16px; border-bottom: 1px solid #f1f5f9; } | ||
#mippedia-data-app.md-dark .md-header { border-color: #334155; } | #mippedia-data-app.md-dark .md-header { border-color: #334155; } | ||
.md-title-container { display: flex; align-items: center; gap: | .md-title-container { display: flex; align-items: center; gap: 10px; flex: 1 1 auto; } | ||
.md-title { font-size: 1. | .md-title { font-size: 1.25rem; font-weight: 700; color: #0f172a; margin: 0; letter-spacing: -0.5px; display:flex; align-items:center; gap:8px;} | ||
.md-actions { display: flex; gap: | .md-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; } | ||
.md-btn-icon { | .md-btn-icon { | ||
background: #f8fafc; border: 1px solid #cbd5e1; cursor: pointer; color: # | background: #f8fafc; border: 1px solid #cbd5e1; cursor: pointer; color: #3b82f6; | ||
padding: | padding: 8px 14px; border-radius: 6px; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); | ||
display: inline-flex; align-items: center; gap: | display: inline-flex; align-items: center; gap: 6px; font-size: 0.85rem; font-weight: 600; white-space: nowrap; | ||
} | } | ||
.md-btn-icon:hover:not(:disabled) { background: # | .md-btn-icon:hover:not(:disabled) { background: #eff6ff; border-color: #bfdbfe; transform: translateY(-1px); } | ||
#mippedia-data-app.md-dark .md-btn-icon { background: #1e293b; border-color: #475569; color: # | #mippedia-data-app.md-dark .md-btn-icon { background: #1e293b; border-color: #475569; color: #60a5fa; } | ||
.md-btn-lock { color: # | #mippedia-data-app.md-dark .md-btn-icon:hover:not(:disabled) { background: #2dd4bf1a; border-color: #2dd4bf; } | ||
.md-btn-lock { color: #ef4444; background: #fef2f2; border-color: #fecaca; } | |||
/* | /* Stats & Score Bar */ | ||
.md- | .md-dashboard { display: grid; grid-template-columns: 1fr auto; gap: 16px; margin-bottom: 20px; align-items: center; } | ||
.md-score-box { background: #f8fafc; padding: 12px 16px; border-radius: 8px; border: 1px solid #e2e8f0; width: 100%; } | |||
#mippedia-data-app.md-dark .md-score-box { background: #182234; border-color: #334155; } | #mippedia-data-app.md-dark .md-score-box { background: #182234; border-color: #334155; } | ||
.md-score-bar-bg { background: #e2e8f0; height: | .md-score-bar-bg { background: #e2e8f0; height: 4px; border-radius: 4px; overflow: hidden; margin-top: 8px; } | ||
.md-score-bar-fill { background: linear-gradient(90deg, # | .md-score-bar-fill { background: linear-gradient(90deg, #3b82f6, #10b981); height: 100%; transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1); } | ||
.md-stats-group { display: flex; gap: 8px; font-size: 0.75rem; font-weight: 600; color: #64748b; flex-wrap: wrap; } | |||
.md-stat-badge { background: #f1f5f9; padding: 4px 10px; border-radius: 20px; border: 1px solid #cbd5e1; } | |||
#mippedia-data-app.md-dark .md-stat-badge { background: #0f172a; border-color: #475569; color: #94a3b8; } | |||
.md-search-box { position: relative; margin-bottom: | .md-search-box { position: relative; margin-bottom: 16px; width: 100%; } | ||
.md-search-input { width: 100%; padding: | .md-search-input { width: 100%; padding: 10px 14px 10px 38px; border: 1px solid #cbd5e1; border-radius: 8px; font-size: 0.9rem; background: #f8fafc; transition: all 0.2s; outline: none; } | ||
.md-search-icon { position: absolute; left: | .md-search-input:focus { border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); background: #ffffff; } | ||
.md-search-icon { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: #94a3b8; } | |||
/* List Rows */ | |||
.md-list { display: flex; flex-direction: column; gap: 10px; } | |||
.md-row-display { | .md-row-display { | ||
display: flex; justify-content: space-between; align-items: flex-start; | display: flex; justify-content: space-between; align-items: flex-start; | ||
padding: | padding: 16px; border: 1px solid #e2e8f0; transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); | ||
border-radius: | border-radius: 8px; gap: 16px; background: #ffffff; | ||
} | } | ||
.md-row-display:hover { border-color: # | .md-row-display:hover { border-color: #bfdbfe; background: #fafafa; } | ||
#mippedia-data-app.md-dark .md-row-display:hover { border-color: #475569; background: #1e293b; } | |||
.md-row-highlight { border-color: #3b82f6 !important; box-shadow: 0 0 0 1px #3b82f6; } | |||
.md-data-group { display: flex; flex-direction: column; gap: 6px; flex: 1; min-width: 0; } | |||
.md-data-label-wrap { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } | |||
.md-data-label { font-size: 0.75rem; color: #64748b; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; } | |||
.md- | /* Professional Tags */ | ||
.md- | .md-type-tag { font-size: 0.65rem; padding: 2px 8px; border-radius: 4px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; border: 1px solid transparent; } | ||
.md- | .md-tag-date { color: #1d4ed8; background: #dbeafe; border-color: #bfdbfe; } | ||
.md- | .md-tag-link { color: #be123c; background: #ffe4e6; border-color: #fecdd3; } | ||
.md-data-value-wrap { display: flex; align-items: center; gap: | .md-tag-piped { color: #b45309; background: #fef3c7; border-color: #fde68a; } | ||
.md-data-value { font-size: | .md-tag-wiki { color: #15803d; background: #dcfce7; border-color: #bbf7d0; } | ||
.md-tag-text { color: #475569; background: #f1f5f9; border-color: #cbd5e1; } | |||
.md-data-value-wrap { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; } | |||
.md-data-value { font-size: 1rem; color: #0f172a; line-height: 1.5; font-weight: 600; word-wrap: break-word; overflow-wrap: break-word; } | |||
.md-btn-mini { background: none; border: none; color: #94a3b8; cursor: pointer; padding: | .md-btn-mini { background: none; border: none; color: #94a3b8; cursor: pointer; padding: 4px 6px; border-radius: 4px; transition: 0.2s; display: inline-flex; align-items: center; } | ||
.md-btn-mini:hover { color: # | .md-btn-mini:hover { color: #3b82f6; background: #eff6ff; } | ||
.md-ref-container { margin-top: | /* References */ | ||
.md-ref-card { background: #f8fafc; padding: | .md-ref-container { margin-top: 10px; width: 100%; } | ||
.md-ref-tag-header { font-size: 0.7rem; font-weight: | .md-ref-card { background: #f8fafc; padding: 12px; border-radius: 6px; border-left: 3px solid #cbd5e1; font-size: 0.85rem; display: flex; flex-direction: column; gap: 4px; } | ||
.md-ref-header { font-weight: | .md-ref-tag-header { font-size: 0.7rem; font-weight: 700; text-transform: uppercase; color: #64748b; display: flex; align-items: center; gap: 6px; margin-bottom: 4px; } | ||
.md-ref-header { font-weight: 600; color: #2563eb; text-decoration: none; word-wrap: break-word; overflow-wrap: break-word; } | |||
.md-social-group { display: flex; flex-wrap: wrap; gap: | /* Socials */ | ||
.md-social-badge { display: inline-flex; align-items: center; gap: | .md-social-group { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; } | ||
.md-social-badge:hover { | .md-social-badge { display: inline-flex; align-items: center; gap: 6px; padding: 4px 12px; border-radius: 20px; font-size: 0.75rem; font-weight: 600; color: white; text-decoration: none; transition: opacity 0.2s; } | ||
.md-social-badge:hover { opacity: 0.9; color: white; text-decoration: none; } | |||
.md-preset-wrapper { display: flex; flex-wrap: wrap; gap: | .md-preset-wrapper { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; } | ||
.md-preset-pill { background: #f1f5f9; border: 1px solid #cbd5e1; padding: | .md-preset-pill { background: #f1f5f9; border: 1px solid #cbd5e1; padding: 4px 10px; border-radius: 16px; font-size: 0.75rem; font-weight: 600; color: #475569; cursor: pointer; transition: 0.2s; } | ||
.md-preset-pill:hover { background: # | .md-preset-pill:hover { background: #eff6ff; color: #1e40af; border-color: #bfdbfe; } | ||
.md-form-box { background: #ffffff; border-radius: | /* Form UI */ | ||
.md-form-title { font-size: 1rem; font-weight: | .md-form-box { background: #ffffff; border-radius: 8px; padding: 20px; margin-top: 20px; border: 1px solid #cbd5e1; transition: all 0.3s; } | ||
.md-form-group { margin-bottom: | .md-form-header { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #e2e8f0; padding-bottom: 12px; margin-bottom: 16px; } | ||
.md-form-group label { display: block; font-size: 0. | .md-form-title { font-size: 1.1rem; font-weight: 700; color: #0f172a; margin: 0; } | ||
.md-input, .md-select, .md-textarea { width: 100%; padding: | |||
.md-input:focus, .md-select:focus { border-color: # | .md-form-group { margin-bottom: 16px; width: 100%; } | ||
.md-form-group label { display: block; font-size: 0.85rem; font-weight: 600; margin-bottom: 6px; color: #334155; } | |||
.md-input, .md-select, .md-textarea { width: 100%; padding: 10px 12px; border: 1px solid #cbd5e1; border-radius: 6px; box-sizing: border-box; font-family: inherit; font-size: 0.9rem; transition: all 0.2s; background: #fff; outline: none; } | |||
.md-input:focus, .md-select:focus { border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); } | |||
.md-input.md-error { border-color: #ef4444; background: #fef2f2; } | |||
.md-dup-alert { background: # | .md-char-count { font-size: 0.7rem; color: #94a3b8; text-align: right; margin-top: 4px; font-weight: 600; } | ||
.md-preview-card { background: # | .md-dup-alert { background: #fffbeb; border: 1px solid #fde68a; color: #b45309; padding: 8px 12px; border-radius: 6px; font-size: 0.8rem; margin-top: 6px; display: none; font-weight: 600; } | ||
.md-preview-card { background: #f8fafc; border: 1px dashed #cbd5e1; padding: 12px; border-radius: 6px; margin-top: 16px; font-size: 0.85rem; } | |||
.md-ref-grid, .md-social-grid { display: grid; grid-template-columns: 1fr 1fr; gap: | .md-ref-grid, .md-social-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; background: #f8fafc; padding: 16px; border-radius: 6px; border: 1px solid #e2e8f0; } | ||
.md-social-grid { max-height: | .md-social-grid { max-height: 250px; overflow-y: auto; } | ||
.md-grid-full { grid-column: span 2; } | .md-grid-full { grid-column: span 2; } | ||
.md-form-actions { display: flex; gap: | .md-form-actions { display: flex; gap: 10px; align-items: center; margin-top: 20px; padding-top: 16px; border-top: 1px solid #e2e8f0; flex-wrap: wrap; } | ||
.md-btn { padding: | .md-btn { padding: 10px 18px; border: none; border-radius: 6px; font-weight: 600; cursor: pointer; color: white; font-size: 0.85rem; transition: 0.2s; display: inline-flex; align-items: center; gap: 6px; justify-content: center; } | ||
.md-btn-save { background: | .md-btn-save { background: #3b82f6; flex: 1; min-width: 150px; } | ||
.md-btn-save:hover { background: #2563eb; } | |||
.md-btn-cancel { background: #64748b; } | .md-btn-cancel { background: #64748b; } | ||
.md-btn-delete { background: # | .md-btn-cancel:hover { background: #475569; } | ||
.md-btn-delete { background: #ef4444; margin-left: auto; } | |||
.md-btn-delete:hover { background: #dc2626; } | |||
.md-btn-reset { background: transparent; border: 1px solid #cbd5e1; color: #64748b; } | |||
.md-btn-reset:hover { background: #f1f5f9; color: #0f172a; } | |||
.md-spinner { display: none; width: | .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.6s linear infinite; } | ||
.is-loading .md-spinner { display: inline-block; } | .is-loading .md-spinner { display: inline-block; } | ||
@keyframes md-spin { to { transform: rotate(360deg); } } | @keyframes md-spin { to { transform: rotate(360deg); } } | ||
@media (max-width: | /* MOBILE OPTIMIZATION (FITUR RESPONSIVE) */ | ||
#mippedia-data-app { padding: | @media (max-width: 768px) { | ||
.md-header { flex-direction: column; align-items: flex-start; gap: 8px; } | #mippedia-data-app { padding: 16px; margin: 16px 0; border-radius: 8px; border-left: none; border-right: none; border-radius: 0; } | ||
.md-actions { width: 100%; justify-content: flex- | .md-header { flex-direction: column; align-items: flex-start; gap: 12px; } | ||
.md-ref-grid, .md-social-grid { grid-template-columns: 1fr; } | .md-actions { width: 100%; display: grid; grid-template-columns: repeat(auto-fill, minmax(110px, 1fr)); gap: 8px; } | ||
.md-btn-icon { justify-content: center; width: 100%; } | |||
.md-dashboard { grid-template-columns: 1fr; gap: 12px; } | |||
.md-row-display { flex-direction: column; padding: 14px; } | |||
.md-row-actions { width: 100%; display: flex; justify-content: flex-end; padding-top: 8px; border-top: 1px solid #f1f5f9; } | |||
.md-ref-grid, .md-social-grid { grid-template-columns: 1fr; padding: 12px; } | |||
.md-grid-full { grid-column: span 1; } | .md-grid-full { grid-column: span 1; } | ||
.md-form-actions { flex-direction: column; width: 100%; } | |||
.md-form-actions { flex-direction: column; width: 100%; gap: 10px; } | |||
.md-btn { width: 100%; margin: 0 !important; } | .md-btn { width: 100%; margin: 0 !important; } | ||
} | } | ||
| Baris 267: | Baris 369: | ||
function buildWikitextRef(refObj) { | function buildWikitextRef(refObj) { | ||
if (!refObj) return ""; | if (!refObj) return ""; | ||
var title = refObj.title || " | var title = refObj.title || "Referensi"; | ||
var text = refObj.url ? "[" + ensureHttps(refObj.url) + " " + title + "]" : "'''" + title + "'''"; | var text = refObj.url ? "[" + ensureHttps(refObj.url) + " " + title + "]" : "'''" + title + "'''"; | ||
var details = []; | var details = []; | ||
| Baris 273: | Baris 375: | ||
if (refObj.site) details.push("Situs: " + refObj.site); | if (refObj.site) details.push("Situs: " + refObj.site); | ||
if (refObj.publisher) details.push("Penerbit: " + refObj.publisher); | if (refObj.publisher) details.push("Penerbit: " + refObj.publisher); | ||
if (refObj.date) details.push(" | if (refObj.date) details.push("Akses: " + formatIndonesianDate(refObj.date)); | ||
if (refObj.type) details.push("Jenis: " + refObj.type + (refObj.subType ? " (" + refObj.subType + ")" : "")); | if (refObj.type) details.push("Jenis: " + refObj.type + (refObj.subType ? " (" + refObj.subType + ")" : "")); | ||
if (details.length > 0) text += " — <small>" + details.join(" • ") + "</small>"; | if (details.length > 0) text += " — <small>" + details.join(" • ") + "</small>"; | ||
| Baris 282: | Baris 384: | ||
if ($btnEl) $btnEl.addClass('is-loading').prop('disabled', true); | if ($btnEl) $btnEl.addClass('is-loading').prop('disabled', true); | ||
// Bersihkan LocalStorage Draft saat disave | |||
localStorage.removeItem('md_draft_' + pageName); | |||
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 302: | Baris 407: | ||
api.postWithToken('csrf', { | api.postWithToken('csrf', { | ||
action: 'edit', title: pageName, text: newContent, summary: 'Mippedia Data | action: 'edit', title: pageName, text: newContent, summary: 'Mippedia Data V9: ' + actionType, minor: false | ||
}).done(function() { | }).done(function() { | ||
window.location.href = location.pathname + "?refresh=" + new Date().getTime(); | window.location.href = location.pathname + "?refresh=" + new Date().getTime(); | ||
| Baris 317: | Baris 422: | ||
// HEADER & TOOLS | // HEADER & TOOLS | ||
var $header = $('<div class="md-header"></div>'); | var $header = $('<div class="md-header"></div>'); | ||
var $titleContainer = $('<div class="md-title-container"><h3 class="md-title">' + | var $titleContainer = $('<div class="md-title-container"><h3 class="md-title">' + svgDatabase + ' Structured Data</h3></div>'); | ||
var $actions = $('<div class="md-actions"></div>'); | var $actions = $('<div class="md-actions"></div>'); | ||
var $darkBtn = $('<button class="md-btn-icon" title="Theme Toggle">' + svgMoon + ' Theme</button>'); | |||
var $darkBtn = $('<button class="md-btn-icon" title=" | |||
$darkBtn.on('click', function() { | $darkBtn.on('click', function() { | ||
isDarkMode = !isDarkMode; | isDarkMode = !isDarkMode; | ||
| Baris 328: | Baris 432: | ||
$actions.append($darkBtn); | $actions.append($darkBtn); | ||
// FITUR | // FITUR 6: Read-Only Mode | ||
var $ | var $viewBtn = $('<button class="md-btn-icon" title="View Only Mode">' + svgView + ' View</button>'); | ||
$ | $viewBtn.on('click', function() { | ||
$actions.append($ | isReadOnly = !isReadOnly; | ||
$app.find('.md-row-actions, .md-btn-mini').toggle(!isReadOnly); | |||
$(this).css('background', isReadOnly ? '#e0e7ff' : ''); | |||
}); | |||
$actions.append($viewBtn); | |||
$actions.append($('<button class="md-btn-icon" title="Export JSON">' + svgCode + ' JSON</button>').on('click', function() { showJsonModal(rawContent); })); | |||
// FITUR 4: CSV Export | |||
$actions.append($('<button class="md-btn-icon" title="Export CSV">' + svgCsv + ' CSV</button>').on('click', exportToCSV)); | |||
if (isSysop) { | if (isSysop) { | ||
var $lockBtn = $('<button class="md-btn-icon ' + (isLocked ? 'md-btn-unlock' : 'md-btn-lock') + '">' + (isLocked ? svgUnlock + ' | var $lockBtn = $('<button class="md-btn-icon ' + (isLocked ? 'md-btn-unlock' : 'md-btn-lock') + '">' + (isLocked ? svgUnlock + ' Unlock' : svgLock + ' Lock') + ' <span class="md-spinner"></span></button>'); | ||
$lockBtn.on('click', function() { | $lockBtn.on('click', function() { | ||
isLocked = !isLocked; | isLocked = !isLocked; | ||
| Baris 343: | Baris 456: | ||
if (!isLocked || isSysop) { | if (!isLocked || isSysop) { | ||
var $addBtn = $('<button class="md-btn-icon" | var $addBtn = $('<button class="md-btn-icon" style="background:#3b82f6; color:#fff; border-color:#2563eb;">' + svgEdit + ' Add New</button>'); | ||
$addBtn.on('click', function() { showForm(-1, rawContent); }); | $addBtn.on('click', function() { showForm(-1, rawContent); }); | ||
$actions.append($addBtn); | $actions.append($addBtn); | ||
| Baris 351: | Baris 464: | ||
$app.append($header); | $app.append($header); | ||
// FITUR | // FITUR 10: DASHBOARD STATISTIK & SKOR KUALITAS | ||
var $dashboard = $('<div class="md-dashboard"></div>'); | |||
var score = calculateQualityScore(); | var score = calculateQualityScore(); | ||
var $scoreBox = $('<div class="md-score-box"><div style="display:flex; justify-content:space-between; font-size:0. | var $scoreBox = $('<div class="md-score-box"><div style="display:flex; justify-content:space-between; font-size:0.8rem; font-weight:700;"><span>Data Completeness</span><span>' + score + '%</span></div><div class="md-score-bar-bg"><div class="md-score-bar-fill" style="width:' + score + '%;"></div></div></div>'); | ||
$app.append($ | $dashboard.append($scoreBox); | ||
var stats = calculateDataStats(); | |||
var $statsGroup = $('<div class="md-stats-group"></div>'); | |||
$statsGroup.append('<span class="md-stat-badge">Text: ' + stats.text + '</span>'); | |||
$statsGroup.append('<span class="md-stat-badge">Link: ' + stats.wiki + '</span>'); | |||
$statsGroup.append('<span class="md-stat-badge">Ext. Web: ' + stats.link + '</span>'); | |||
$statsGroup.append('<span class="md-stat-badge">Date: ' + stats.date + '</span>'); | |||
$dashboard.append($statsGroup); | |||
$app.append($dashboard); | |||
// SEARCH BAR | // SEARCH BAR & FITUR 15 (Highlighting) | ||
if (dataItems.length > | if (dataItems.length > 0) { | ||
var $searchBox = $('<div class="md-search-box"><span class="md-search-icon">' + svgSearch + '</span><input type="text" class="md-search-input" placeholder=" | var $searchBox = $('<div class="md-search-box"><span class="md-search-icon">' + svgSearch + '</span><input type="text" class="md-search-input" placeholder="Search data..."></div>'); | ||
$searchBox.find('input').on('keyup input', function() { | $searchBox.find('input').on('keyup input', function() { | ||
var q = $(this).val().toLowerCase(); | var q = $(this).val().toLowerCase(); | ||
$app.find('.md-row-display').each(function() { | $app.find('.md-row-display').each(function() { | ||
var text = $(this).text().toLowerCase(); | var text = $(this).text().toLowerCase(); | ||
$(this).toggle( | var match = text.indexOf(q) !== -1; | ||
$(this).toggle(match); | |||
if (q.length > 1 && match) { $(this).addClass('md-row-highlight'); } | |||
else { $(this).removeClass('md-row-highlight'); } | |||
}); | }); | ||
}); | }); | ||
| Baris 369: | Baris 495: | ||
} | } | ||
// DAFTAR BARIS DATA | // DAFTAR BARIS DATA (TANPA NAIK/TURUN) | ||
var $list = $('<div class="md-list"></div>'); | var $list = $('<div class="md-list"></div>'); | ||
if (dataItems.length === 0) { | if (dataItems.length === 0) { | ||
$list.append('<div style="color:#64748b; text-align:center; padding: | $list.append('<div style="color:#64748b; text-align:center; padding: 30px 10px; background:#f8fafc; border-radius:8px; border:1px dashed #cbd5e1; font-size:0.9rem;">No data entries found. Click <b>Add New</b> to create.</div>'); | ||
} else { | } else { | ||
dataItems.forEach(function(item, index) { | dataItems.forEach(function(item, index) { | ||
| Baris 378: | Baris 504: | ||
var $info = $('<div class="md-data-group"></div>'); | var $info = $('<div class="md-data-group"></div>'); | ||
var typeBadge = detectDataTypeBadge(item); | var typeBadge = detectDataTypeBadge(item); | ||
$info.append('<div class="md-data-label-wrap"><span class="md-data-label">' + item.prop + '</span>' + typeBadge + '</div>'); | $info.append('<div class="md-data-label-wrap"><span class="md-data-label">' + item.prop + '</span>' + typeBadge + '</div>'); | ||
var displayValHtml = ''; | var displayValHtml = ''; | ||
var rawVal = item.val || ''; | var rawVal = item.val || ''; | ||
if (rawVal.match(/^https?:\/\//i)) { | if (rawVal.match(/^https?:\/\//i)) { | ||
displayValHtml = '<a href="' + rawVal + '" target="_blank" style="color:#2563eb; font-weight: | displayValHtml = '<a href="' + rawVal + '" target="_blank" style="color:#2563eb; font-weight:600; 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: | displayValHtml = '<a href="' + mw.util.getUrl(item.linkTarget.trim()) + '" style="color:#2563eb; font-weight:600; text-decoration:none;">' + 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 395: | Baris 519: | ||
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: | return '<a href="' + mw.util.getUrl(trimmed) + '" style="color:#2563eb; font-weight:600; text-decoration:none;">' + trimmed + '</a>'; | ||
} | } | ||
return p; | return p; | ||
}).join(''); | }).join(''); | ||
} else if (item.link) { | } else if (item.link) { | ||
displayValHtml = '<a href="' + mw.util.getUrl(rawVal) + '" style="color:#2563eb; font-weight: | displayValHtml = '<a href="' + mw.util.getUrl(rawVal) + '" style="color:#2563eb; font-weight:600; text-decoration:none;">' + rawVal + '</a>'; | ||
} else { | } else { | ||
displayValHtml = rawVal; | displayValHtml = rawVal; | ||
| Baris 408: | Baris 532: | ||
$valWrap.append('<span class="md-data-value">' + displayValHtml + '</span>'); | $valWrap.append('<span class="md-data-value">' + displayValHtml + '</span>'); | ||
var $copyBtn = $('<button class="md-btn-mini" title=" | var $copyBtn = $('<button class="md-btn-mini" title="Copy Text">' + svgCopy + '</button>'); | ||
$copyBtn.on('click', function() { | $copyBtn.on('click', function() { | ||
navigator.clipboard.writeText(rawVal); | navigator.clipboard.writeText(rawVal); | ||
| Baris 416: | Baris 540: | ||
$valWrap.append($copyBtn); | $valWrap.append($copyBtn); | ||
var $copyWtBtn = $('<button class="md-btn-mini" title="Copy Wikitext">' + svgWikitext + '</button>'); | |||
var $copyWtBtn = $('<button class="md-btn-mini" title=" | |||
$copyWtBtn.on('click', function() { | $copyWtBtn.on('click', function() { | ||
var wtCode = "| " + item.prop + " = " + buildWikitextVal(item); | var wtCode = "| " + item.prop + " = " + buildWikitextVal(item); | ||
navigator.clipboard.writeText(wtCode); | navigator.clipboard.writeText(wtCode); | ||
$(this).css('color', '# | $(this).css('color', '#3b82f6'); | ||
setTimeout(function(){ $copyWtBtn.css('color', '#94a3b8'); }, 1000); | setTimeout(function(){ $copyWtBtn.css('color', '#94a3b8'); }, 1000); | ||
}); | }); | ||
| Baris 428: | Baris 551: | ||
$info.append($valWrap); | $info.append($valWrap); | ||
// | // MEDIA SOSIAL | ||
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 445: | Baris 568: | ||
} | } | ||
// | // REFERENSI | ||
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; | ||
var $refContainer = $('<div class="md-ref-container"></div>'); | var $refContainer = $('<div class="md-ref-container"></div>'); | ||
var $refCard = $('<div class="md-ref-card"></div>'); | var $refCard = $('<div class="md-ref-card"></div>'); | ||
$refCard.append('<div class="md-ref-tag-header">' + svgRef + ' | $refCard.append('<div class="md-ref-tag-header">' + svgRef + ' Reference Source:</div>'); | ||
var refTitle = ref.title || " | var refTitle = ref.title || "Reference Link"; | ||
if (ref.url) { | if (ref.url) { | ||
$refCard.append('<a href="' + ensureHttps(ref.url) + '" target="_blank" class="md-ref-header">' + refTitle + '</a>'); | $refCard.append('<a href="' + ensureHttps(ref.url) + '" target="_blank" class="md-ref-header">' + refTitle + '</a>'); | ||
| Baris 460: | Baris 583: | ||
var details = []; | var details = []; | ||
if (ref.author) details.push(" | if (ref.author) details.push("Author: " + ref.author); | ||
if (ref.site) details.push(" | if (ref.site) details.push("Site: " + ref.site); | ||
if (ref.publisher) details.push(" | if (ref.publisher) details.push("Publisher: " + ref.publisher); | ||
if (ref.date) details.push(" | if (ref.date) details.push("Date: " + formatIndonesianDate(ref.date)); | ||
if (details.length > 0) { | if (details.length > 0) { | ||
$refCard.append('<div style="font-size:0. | $refCard.append('<div style="font-size:0.75rem; color:#64748b; margin-top:2px;">' + details.join(' • ') + '</div>'); | ||
} | } | ||
| Baris 474: | Baris 597: | ||
$row.append($info); | $row.append($info); | ||
// | // ACTIONS PER BARIS (Edit, Clone) | ||
var $ctrls = $('<div style="display:flex; align-items:center; gap: | var $ctrls = $('<div class="md-row-actions" style="display:flex; align-items:center; gap:8px;"></div>'); | ||
if (!isLocked || isSysop) { | if (!isLocked || isSysop) { | ||
// FITUR 5: Quick Clone Data | |||
var $cloneBtn = $('<button class="md-btn-icon" style="padding:6px 10px;" title="Clone Row">' + svgClone + ' Clone</button>'); | |||
$cloneBtn.on('click', function() { | |||
var clonedObj = JSON.parse(JSON.stringify(item)); | |||
clonedObj.prop = clonedObj.prop + " (Copy)"; | |||
dataItems.splice(index + 1, 0, clonedObj); | |||
saveData('Kloning data', $(this), rawContent); | |||
}); | |||
$ctrls.append($cloneBtn); | |||
var $editIcon = $('<button class="md-btn-icon | var $editIcon = $('<button class="md-btn-icon" style="padding:6px 10px;">' + svgEdit + ' Edit</button>'); | ||
$editIcon.on('click', function() { showForm(index, rawContent); }); | $editIcon.on('click', function() { showForm(index, rawContent); }); | ||
$ctrls.append($editIcon); | $ctrls.append($editIcon); | ||
| Baris 515: | Baris 628: | ||
} | } | ||
// 7. FORM EDIT | // 7. FORM EDIT (SMART FEATURES IMPLEMENTATION) | ||
function showForm(index, rawContent) { | function showForm(index, rawContent) { | ||
var $container = $('#md-form-container'); | var $container = $('#md-form-container'); | ||
| Baris 522: | Baris 635: | ||
var isNew = index === -1; | var isNew = index === -1; | ||
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]; | ||
// FITUR 3: Cek Draft dari Local Storage jika ini Form Baru | |||
if (isNew) { | |||
var draft = localStorage.getItem('md_draft_' + pageName); | |||
if (draft) { | |||
if (confirm("Draft formulir sebelumnya ditemukan. Ingin memuatnya?")) { | |||
try { currentData = JSON.parse(draft); } catch(e){} | |||
} else { localStorage.removeItem('md_draft_' + pageName); } | |||
} | |||
} | |||
var currentRef = currentData.ref || {}; | var currentRef = currentData.ref || {}; | ||
var currentSocials = currentData.socials || {}; | var currentSocials = currentData.socials || {}; | ||
var $form = $('<div class="md-form-box" | var $form = $('<div class="md-form-box" id="md-main-form"></div>'); | ||
// FITUR | // FITUR 11: Form Accordion | ||
var $propGroup = $('<div class="md-form-group"><label>Label | var $header = $('<div class="md-form-header"></div>'); | ||
$header.append('<h4 class="md-form-title">' + (isNew ? 'Add New Data Entry' : 'Edit Data Entry') + '</h4>'); | |||
var $toggleBtn = $('<button class="md-btn-mini">Minimize</button>'); | |||
$header.append($toggleBtn); | |||
$form.append($header); | |||
var $formBody = $('<div></div>'); | |||
// Label Box | |||
var $propGroup = $('<div class="md-form-group"><label>Data Label (Required)</label><input type="text" id="md-inp-prop" class="md-input" value="' + (currentData.prop || '') + '"><div class="md-dup-alert" id="md-dup-msg">Alert: This label already exists in the dataset!</div></div>'); | |||
var $presetWrap = $('<div class="md-preset-wrapper"></div>'); | var $presetWrap = $('<div class="md-preset-wrapper"></div>'); | ||
PRESET_LABELS.forEach(function(lbl) { | PRESET_LABELS.forEach(function(lbl) { | ||
var $pill = $('<span class="md-preset-pill">' + lbl + '</span>'); | var $pill = $('<span class="md-preset-pill">' + lbl + '</span>'); | ||
$pill.on('click', function() { | $pill.on('click', function() { $('#md-inp-prop').val(lbl).trigger('input'); }); | ||
$presetWrap.append($pill); | $presetWrap.append($pill); | ||
}); | }); | ||
$propGroup.append($presetWrap); | $propGroup.append($presetWrap); | ||
$ | $formBody.append($propGroup); | ||
// Value Box | // Value Box + FITUR 7 (Char Counter) | ||
var $valBox = $('<div class="md-form-group"></div>'); | var $valBox = $('<div class="md-form-group"></div>'); | ||
$valBox.append('<label> | $valBox.append('<label>Data Value (Required)</label><input type="text" id="md-inp-val" class="md-input" value="' + (currentData.val || '') + '">'); | ||
$valBox.append('<div class="md-char-count" id="md-char-counter">0 chars</div>'); | |||
var $wikilinkOpts = $('<div style="margin-top: | var $wikilinkOpts = $('<div style="margin-top:10px; display:flex; flex-direction:column; gap:8px; background:#f8fafc; padding:12px; border-radius:6px; border:1px solid #e2e8f0;"></div>'); | ||
var chkLink = currentData.link ? 'checked' : ''; | var chkLink = currentData.link ? 'checked' : ''; | ||
var chkSplit = currentData.linkSplit ? 'checked' : ''; | var chkSplit = currentData.linkSplit ? 'checked' : ''; | ||
$wikilinkOpts.append('<label style="display:flex; align-items:center; gap: | $wikilinkOpts.append('<label style="display:flex; align-items:center; gap:8px; font-size:0.85rem;"><input type="checkbox" id="md-inp-link" ' + chkLink + '> Standard Wikilink [[Title]]</label>'); | ||
$wikilinkOpts.append('<label style="display:flex; align-items:center; gap: | $wikilinkOpts.append('<label style="display:flex; align-items:center; gap:8px; font-size:0.85rem;"><input type="checkbox" id="md-inp-split" ' + chkSplit + '> Smart Split Wikilink</label>'); | ||
$wikilinkOpts.append('<div><label style="font-size:0. | $wikilinkOpts.append('<div style="margin-top:4px;"><label style="font-size:0.8rem; color:#475569;">Custom Target (Piped Link):</label><input type="text" id="md-inp-target" class="md-input" style="padding:8px 10px; font-size:0.85rem;" value="' + (currentData.linkTarget || '') + '"></div>'); | ||
$valBox.append($wikilinkOpts); | $valBox.append($wikilinkOpts); | ||
$ | $formBody.append($valBox); | ||
// | // Media Sosial | ||
var $socialSec = $('<div class="md-form-group" style="margin-top: | var $socialSec = $('<div class="md-form-group" style="margin-top:20px;"></div>'); | ||
var chkPerson = currentData.isPersonOrg ? 'checked' : ''; | var chkPerson = currentData.isPersonOrg ? 'checked' : ''; | ||
$socialSec.append('<label style="display:flex; align-items:center; gap: | $socialSec.append('<label style="display:flex; align-items:center; gap:8px; cursor:pointer; background:#eff6ff; padding:10px; border-radius:6px; color:#1e40af; border:1px solid #bfdbfe;"><input type="checkbox" id="md-chk-person" ' + chkPerson + '> Enable Social Media & Music Links</label>'); | ||
var $socialGrid = $('<div class="md-social-grid" id="md-social-box" style="display:' + (currentData.isPersonOrg ? 'grid' : 'none') + '; margin-top: | var $socialGrid = $('<div class="md-social-grid" id="md-social-box" style="display:' + (currentData.isPersonOrg ? 'grid' : 'none') + '; margin-top:8px;"></div>'); | ||
Object.keys(SOCIAL_PLATFORMS).forEach(function(key) { | Object.keys(SOCIAL_PLATFORMS).forEach(function(key) { | ||
var p = SOCIAL_PLATFORMS[key]; | var p = SOCIAL_PLATFORMS[key]; | ||
var valUname = currentSocials[key] || ''; | var valUname = currentSocials[key] || ''; | ||
var $socInp = $('<input type="text" id="md-soc-' + key + '" class="md-input" placeholder="ID | var $socInp = $('<input type="text" id="md-soc-' + key + '" class="md-input" placeholder="Username / ID" value="' + valUname + '">'); | ||
$socInp.on('blur paste input', function() { $(this).val(cleanHandle($(this).val())); }); | |||
$socInp.on('blur paste input', function() { | $socialGrid.append('<div><label style="font-size:0.8rem;">' + p.name + '</label></div>').find('div').last().append($socInp); | ||
$socialGrid.append('<div><label style="font-size:0. | |||
}); | }); | ||
$socialSec.append($socialGrid); | $socialSec.append($socialGrid); | ||
$ | $formBody.append($socialSec); | ||
// | // Referensi | ||
var $refArea = $('<div class="md-form-group" style="margin-top: | var $refArea = $('<div class="md-form-group" style="margin-top:20px;"></div>'); | ||
$refArea.append('<label style="font-size:0. | $refArea.append('<label style="font-size:0.95rem; border-bottom:1px solid #e2e8f0; padding-bottom:6px;">Reference Source</label>'); | ||
var $refGrid = $('<div class="md-ref-grid"></div>'); | var $refGrid = $('<div class="md-ref-grid" style="margin-top:10px;"></div>'); | ||
var $typeSel = $('<select id="md-ref-type" class="md-select"><option value="">-- | var $typeSel = $('<select id="md-ref-type" class="md-select"><option value="">-- Main Type --</option></select>'); | ||
Object.keys(REF_TYPES).forEach(function(t) { | Object.keys(REF_TYPES).forEach(function(t) { | ||
var sel = currentRef.type === t ? 'selected' : ''; | var sel = currentRef.type === t ? 'selected' : ''; | ||
| Baris 588: | Baris 714: | ||
}); | }); | ||
var $subTypeSel = $('<select id="md-ref-subtype" class="md-select"><option value="">-- Sub | var $subTypeSel = $('<select id="md-ref-subtype" class="md-select"><option value="">-- Sub Type --</option></select>'); | ||
function updateSubTypes(mainType, selectedSub) { | function updateSubTypes(mainType, selectedSub) { | ||
$subTypeSel.empty().append('<option value="">-- Sub | $subTypeSel.empty().append('<option value="">-- Sub Type --</option>'); | ||
if (mainType && REF_TYPES[mainType]) { | if (mainType && REF_TYPES[mainType]) { | ||
REF_TYPES[mainType].forEach(function(st) { | REF_TYPES[mainType].forEach(function(st) { | ||
| Baris 601: | Baris 727: | ||
updateSubTypes(currentRef.type, currentRef.subType); | updateSubTypes(currentRef.type, currentRef.subType); | ||
$refGrid.append('<div><label style="font-size:0. | $refGrid.append('<div><label style="font-size:0.8rem;">Main Type</label></div>').find('div').last().append($typeSel); | ||
$refGrid.append('<div><label style="font-size:0. | $refGrid.append('<div><label style="font-size:0.8rem;">Sub Type</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="' + (currentRef.url || '') + '">'); | ||
| Baris 609: | Baris 735: | ||
$(this).val(u); | $(this).val(u); | ||
if (u && !$('#md-ref-site').val().trim()) $('#md-ref-site').val(extractDomain(u)); | if (u && !$('#md-ref-site').val().trim()) $('#md-ref-site').val(extractDomain(u)); | ||
if (u && !$('#md-ref-title').val().trim()) $('#md-ref-title').val(" | if (u && !$('#md-ref-title').val().trim()) $('#md-ref-title').val("Reference from " + extractDomain(u)); | ||
}); | }); | ||
$refGrid.append('<div class="md-grid-full"><label style="font-size:0. | $refGrid.append('<div class="md-grid-full"><label style="font-size:0.8rem;">Reference URL</label></div>').find('div').last().append($urlInput); | ||
$refGrid.append('<div><label style="font-size:0. | $refGrid.append('<div><label style="font-size:0.8rem;">Article Title</label><input type="text" id="md-ref-title" class="md-input" value="' + (currentRef.title || '') + '"></div>'); | ||
$refGrid.append('<div><label style="font-size:0. | $refGrid.append('<div><label style="font-size:0.8rem;">Site Name</label><input type="text" id="md-ref-site" class="md-input" value="' + (currentRef.site || '') + '"></div>'); | ||
$refGrid.append('<div><label style="font-size:0. | $refGrid.append('<div><label style="font-size:0.8rem;">Author</label><input type="text" id="md-ref-author" class="md-input" value="' + (currentRef.author || '') + '"></div>'); | ||
$refGrid.append('<div><label style="font-size:0. | $refGrid.append('<div><label style="font-size:0.8rem;">Publisher</label><input type="text" id="md-ref-publisher" class="md-input" value="' + (currentRef.publisher || '') + '"></div>'); | ||
$refArea.append($refGrid); | $refArea.append($refGrid); | ||
$ | $formBody.append($refArea); | ||
var $previewBox = $('<div class="md-preview-card"><b>Live Preview:</b><div id="md-live-prev" style="margin-top:6px;"></div></div>'); | |||
var $previewBox = $('<div class="md-preview-card"><b> | $formBody.append($previewBox); | ||
$ | |||
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"> | var $btnSave = $('<button class="md-btn md-btn-save">Save Entry <span class="md-spinner"></span></button>'); | ||
var $btnCancel = $('<button class="md-btn md-btn-cancel"> | var $btnCancel = $('<button class="md-btn md-btn-cancel">Cancel</button>'); | ||
$actions.append($btnSave).append($btnCancel); | |||
// FITUR 14: Fast Reset | |||
var $btnReset = $('<button class="md-btn md-btn-reset">Reset</button>'); | |||
$btnReset.on('click', function() { | |||
$formBody.find('input[type="text"], input[type="url"]').val(''); | |||
$formBody.find('input[type="checkbox"]').prop('checked', false); | |||
$formBody.find('select').prop('selectedIndex', 0); | |||
$('#md-social-box').hide(); | |||
updateLivePreview(); | |||
}); | |||
$actions.append($btnSave).append($btnReset).append($btnCancel); | |||
if (!isNew) { | if (!isNew) { | ||
var $btnDelete = $('<button class="md-btn md-btn-delete"> | var $btnDelete = $('<button class="md-btn md-btn-delete">Delete <span class="md-spinner"></span></button>'); | ||
$btnDelete.on('click', function() { | $btnDelete.on('click', function() { | ||
if(confirm(' | if(confirm('Are you sure you want to delete this item?')) { | ||
dataItems.splice(index, 1); | dataItems.splice(index, 1); | ||
saveData('Hapus data', $(this), rawContent); | saveData('Hapus data', $(this), rawContent); | ||
| Baris 641: | Baris 776: | ||
$actions.append($btnDelete); | $actions.append($btnDelete); | ||
} | } | ||
$form.append($ | $formBody.append($actions); | ||
$form.append($formBody); | |||
$container.append($form); | $container.append($form); | ||
// LIVE PREVIEW | // BINDINGS & LIVE PREVIEW | ||
$toggleBtn.on('click', function() { | |||
$formBody.slideToggle(200); | |||
$(this).text($(this).text() === 'Minimize' ? 'Maximize' : 'Minimize'); | |||
}); | |||
function getFormState() { | |||
var socObj = {}; | |||
if ($('#md-chk-person').is(':checked')) { | |||
Object.keys(SOCIAL_PLATFORMS).forEach(k => { | |||
var v = cleanHandle($('#md-soc-' + k).val()); | |||
if(v) socObj[k] = v; | |||
}); | |||
} | |||
return { | |||
prop: $('#md-inp-prop').val(), | |||
val: $('#md-inp-val').val(), | |||
link: $('#md-inp-link').is(':checked'), | |||
linkSplit: $('#md-inp-split').is(':checked'), | |||
linkTarget: $('#md-inp-target').val(), | |||
isPersonOrg: $('#md-chk-person').is(':checked'), | |||
socials: socObj, | |||
ref: { | |||
type: $('#md-ref-type').val(), subType: $('#md-ref-subtype').val(), | |||
url: $('#md-ref-url').val(), title: $('#md-ref-title').val(), site: $('#md-ref-site').val() | |||
} | |||
}; | |||
} | |||
function updateLivePreview() { | function updateLivePreview() { | ||
var p = $('#md-inp-prop').val().trim() || 'Label'; | var p = $('#md-inp-prop').val().trim() || 'Label'; | ||
var v = $('#md-inp-val').val().trim() || ' | var v = $('#md-inp-val').val().trim() || 'Value'; | ||
$('#md-char-counter').text(v.length + " chars"); | |||
// FITUR 9: Required Validator Highlight | |||
$('#md-inp-prop').toggleClass('md-error', !$('#md-inp-prop').val().trim()); | |||
$('#md-inp-val').toggleClass('md-error', !$('#md-inp-val').val().trim()); | |||
var refT = $('#md-ref-title').val().trim(); | var refT = $('#md-ref-title').val().trim(); | ||
var refU = $('#md-ref-url').val().trim(); | var refU = $('#md-ref-url').val().trim(); | ||
var html = '<div><b>' + p + ':</b> ' + v + '</div>'; | var html = '<div><b style="color:#64748b; text-transform:uppercase; font-size:0.8rem;">' + p + ':</b> <span style="font-weight:600; color:#0f172a;">' + v + '</span></div>'; | ||
if (refT || refU) { | if (refT || refU) { html += '<div style="font-size:0.75rem; color:#3b82f6; margin-top:4px; font-weight:600;">Ref: ' + (refT || refU) + '</div>'; } | ||
$('#md-live-prev').html(html); | $('#md-live-prev').html(html); | ||
var isDup = dataItems.some(function(item, idx) { return idx !== index && item.prop.toLowerCase() === p.toLowerCase(); }); | |||
var isDup = dataItems.some(function(item, idx) { | |||
$('#md-dup-msg').toggle(isDup); | $('#md-dup-msg').toggle(isDup); | ||
// FITUR 3: Save state to localStorage | |||
if (isNew) localStorage.setItem('md_draft_' + pageName, JSON.stringify(getFormState())); | |||
} | } | ||
// FITUR 1: Auto Capitalize Labels & FITUR 2: Smart Space Trimmer | |||
$form.find('input[type="text"]').on('blur', function() { | |||
var val = $(this).val().replace(/\s+/g, ' ').trim(); | |||
if ($(this).attr('id') === 'md-inp-prop') { | |||
val = val.replace(/\b\w/g, function(l) { return l.toUpperCase(); }); | |||
} | |||
$(this).val(val); | |||
updateLivePreview(); | |||
}); | |||
$form.find('input, select').on('keyup input change', updateLivePreview); | $form.find('input, select').on('keyup input change', updateLivePreview); | ||
| Baris 668: | Baris 846: | ||
$('#md-chk-person').on('change', function() { | $('#md-chk-person').on('change', function() { | ||
$('#md-social-box').css('display', $(this).is(':checked') ? 'grid' : 'none'); | |||
updateLivePreview(); | |||
}); | }); | ||
$('html, body').animate({ scrollTop: $form.offset().top - | $('html, body').animate({ scrollTop: $form.offset().top - 40 }, 300, 'swing'); | ||
$btnCancel.on('click', function() { $container.empty(); }); | $btnCancel.on('click', function() { $container.empty(); }); | ||
| Baris 680: | Baris 858: | ||
if (!newProp || !newVal) { | if (!newProp || !newVal) { | ||
alert("Label | alert("Data Label and Value are required!"); return; | ||
} | } | ||
| Baris 730: | Baris 908: | ||
var jsonStr = JSON.stringify(dataItems, null, 2); | var jsonStr = JSON.stringify(dataItems, null, 2); | ||
var $box = $('<div class="md-form-box"></div>'); | var $box = $('<div class="md-form-box"></div>'); | ||
$box.append('<h4 class="md-form-title"> | $box.append('<div class="md-form-header"><h4 class="md-form-title">JSON Import / Export</h4></div>'); | ||
var $txt = $('<textarea class="md-textarea" rows=" | var $txt = $('<textarea class="md-textarea" rows="12" style="font-family:monospace; font-size:0.8rem; background:#f8fafc;"></textarea>').val(jsonStr); | ||
$box.append($txt); | $box.append($txt); | ||
var $actions = $('<div class="md-form-actions"></div>'); | var $actions = $('<div class="md-form-actions"></div>'); | ||
var $btnImport = $('<button class="md-btn md-btn-save"> | var $btnImport = $('<button class="md-btn md-btn-save">Import & Save <span class="md-spinner"></span></button>'); | ||
var $btnCancel = $('<button class="md-btn md-btn-cancel"> | var $btnCancel = $('<button class="md-btn md-btn-cancel">Close</button>'); | ||
$actions.append($btnImport).append($btnCancel); | $actions.append($btnImport).append($btnCancel); | ||
$box.append($actions); | $box.append($actions); | ||
$container.append($box); | $container.append($box); | ||
$('html, body').animate({ scrollTop: $box.offset().top - 40 }, 300); | |||
$btnCancel.on('click', function() { $container.empty(); }); | $btnCancel.on('click', function() { $container.empty(); }); | ||
| Baris 750: | Baris 930: | ||
dataItems = parsed; | dataItems = parsed; | ||
saveData('Impor JSON', $(this), rawContent); | saveData('Impor JSON', $(this), rawContent); | ||
} else { alert(" | } else { alert("JSON format must be an Array!"); } | ||
} catch(e) { alert(" | } catch(e) { alert("Invalid JSON: " + e.message); } | ||
}); | }); | ||
} | } | ||