/**
* Postiz Media Manager - Frontend JavaScript
*/
const API_BASE = window.location.origin;
// State
let allMedia = [];
let selectedMediaIds = new Set();
let protectedMediaIds = new Set();
// Elements
const elements = {
// Stats
totalMedia: document.getElementById('totalMedia'),
protectedMedia: document.getElementById('protectedMedia'),
orphanMedia: document.getElementById('orphanMedia'),
selectedCount: document.getElementById('selectedCount'),
deleteCount: document.getElementById('deleteCount'),
// Filters
filterType: document.getElementById('filterType'),
startDate: document.getElementById('startDate'),
endDate: document.getElementById('endDate'),
applyFilters: document.getElementById('applyFilters'),
clearFilters: document.getElementById('clearFilters'),
// Actions
selectAll: document.getElementById('selectAll'),
deselectAll: document.getElementById('deselectAll'),
deleteSelectedBtn: document.getElementById('deleteSelectedBtn'),
refreshBtn: document.getElementById('refreshBtn'),
// Display
mediaContainer: document.getElementById('mediaContainer'),
loadingIndicator: document.getElementById('loadingIndicator'),
emptyState: document.getElementById('emptyState'),
// Modals
deleteModal: document.getElementById('deleteModal'),
confirmDeleteBtn: document.getElementById('confirmDeleteBtn'),
cancelDeleteBtn: document.getElementById('cancelDeleteBtn'),
modalDeleteCount: document.getElementById('modalDeleteCount'),
resultModal: document.getElementById('resultModal'),
resultIcon: document.getElementById('resultIcon'),
resultTitle: document.getElementById('resultTitle'),
resultMessage: document.getElementById('resultMessage'),
closeResultBtn: document.getElementById('closeResultBtn'),
};
/**
* API Functions
*/
async function fetchStats() {
const response = await fetch(`${API_BASE}/api/stats`);
if (!response.ok) throw new Error('Failed to fetch stats');
return response.json();
}
async function fetchMedia(type = 'all', startDate = '', endDate = '') {
let url = `${API_BASE}/api/media`;
if (type === 'orphans') {
url = `${API_BASE}/api/media/orphans`;
} else if (type === 'published') {
url = `${API_BASE}/api/media/published`;
}
const params = new URLSearchParams();
if (startDate) params.append('startDate', startDate);
if (endDate) params.append('endDate', endDate);
const queryString = params.toString();
if (queryString) url += `?${queryString}`;
const response = await fetch(url);
if (!response.ok) throw new Error('Failed to fetch media');
return response.json();
}
async function fetchProtectedMediaIds() {
const response = await fetch(`${API_BASE}/api/media/protected`);
if (!response.ok) throw new Error('Failed to fetch protected media');
return response.json();
}
async function deleteMedia(mediaIds) {
const response = await fetch(`${API_BASE}/api/media/delete`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ mediaIds }),
});
if (!response.ok) throw new Error('Failed to delete media');
return response.json();
}
/**
* UI Functions
*/
function showLoading() {
elements.loadingIndicator.classList.remove('hidden');
elements.mediaContainer.classList.add('hidden');
elements.emptyState.classList.add('hidden');
}
function hideLoading() {
elements.loadingIndicator.classList.add('hidden');
}
function formatDate(dateString) {
if (!dateString) return 'ไม่ระบุ';
const date = new Date(dateString);
return date.toLocaleDateString('th-TH', {
year: 'numeric',
month: 'short',
day: 'numeric',
});
}
function formatFileSize(bytes) {
if (!bytes) return 'ไม่ระบุ';
if (bytes < 1024) return bytes + ' B';
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
}
function getMediaBadge(mediaId) {
if (protectedMediaIds.has(mediaId)) {
return '<span class="badge badge-success"><i class="fas fa-shield-alt mr-1"></i>ปลอดภัย</span>';
}
return '<span class="badge badge-warning"><i class="fas fa-exclamation-triangle mr-1"></i>ไม่ใช้งาน</span>';
}
function createMediaCard(media) {
const isSelected = selectedMediaIds.has(media.id);
const isProtected = protectedMediaIds.has(media.id);
return `
<div class="media-card bg-white rounded-lg shadow p-4 cursor-pointer ${isSelected ? 'selected' : ''}"
data-media-id="${media.id}"
onclick="toggleMediaSelection('${media.id}')">
<div class="flex items-start justify-between mb-3">
<div class="flex-1">
${getMediaBadge(media.id)}
</div>
<div class="flex items-center">
${isSelected ?
'<i class="fas fa-check-circle text-blue-500 text-xl"></i>' :
'<i class="far fa-circle text-gray-300 text-xl"></i>'}
</div>
</div>
${media.url ? `
<div class="mb-3 rounded-lg overflow-hidden bg-gray-100 h-40 flex items-center justify-center">
${media.mimeType && media.mimeType.startsWith('image/') ?
`<img src="${media.url}" alt="${media.name || 'Media'}" class="w-full h-full object-cover" onerror="this.src='data:image/svg+xml,%3Csvg xmlns=\\'http://www.w3.org/2000/svg\\' width=\\'100\\' height=\\'100\\'%3E%3Crect fill=\\'%23ddd\\' width=\\'100\\' height=\\'100\\'/%3E%3Ctext fill=\\'%23999\\' x=\\'50%25\\' y=\\'50%25\\' text-anchor=\\'middle\\' dy=\\'.3em\\'%3ENo Image%3C/text%3E%3C/svg%3E'">` :
'<i class="fas fa-file text-gray-400 text-4xl"></i>'}
</div>
` : ''}
<div class="space-y-1">
<p class="text-sm font-medium text-gray-900 truncate" title="${media.name || media.id}">
${media.name || media.id}
</p>
<p class="text-xs text-gray-500">
<i class="fas fa-calendar mr-1"></i> ${formatDate(media.createdAt)}
</p>
${media.size ? `
<p class="text-xs text-gray-500">
<i class="fas fa-file mr-1"></i> ${formatFileSize(media.size)}
</p>
` : ''}
<p class="text-xs text-gray-400 truncate" title="${media.id}">
ID: ${media.id}
</p>
</div>
${isProtected ? `
<div class="mt-3 p-2 bg-green-50 border border-green-200 rounded text-xs text-green-700">
<i class="fas fa-info-circle mr-1"></i> Media นี้ถูกใช้ในโพสต์อนาคต ไม่สามารถลบได้
</div>
` : ''}
</div>
`;
}
function renderMedia(mediaList) {
if (mediaList.length === 0) {
elements.mediaContainer.classList.add('hidden');
elements.emptyState.classList.remove('hidden');
return;
}
elements.mediaContainer.classList.remove('hidden');
elements.emptyState.classList.add('hidden');
elements.mediaContainer.innerHTML = mediaList.map(createMediaCard).join('');
}
function updateStats(stats) {
elements.totalMedia.textContent = stats.totalMedia || 0;
elements.protectedMedia.textContent = stats.protectedMedia || 0;
elements.orphanMedia.textContent = stats.orphanMedia || 0;
}
function updateSelectedCount() {
const count = selectedMediaIds.size;
elements.selectedCount.textContent = count;
elements.deleteCount.textContent = count;
elements.deleteSelectedBtn.disabled = count === 0;
}
/**
* Event Handlers
*/
window.toggleMediaSelection = function(mediaId) {
// Check if media is protected
if (protectedMediaIds.has(mediaId)) {
showResult('warning', 'ไม่สามารถเลือกได้', 'Media นี้ถูกใช้ในโพสต์อนาคต ไม่สามารถลบได้');
return;
}
if (selectedMediaIds.has(mediaId)) {
selectedMediaIds.delete(mediaId);
} else {
selectedMediaIds.add(mediaId);
}
updateSelectedCount();
// Re-render to update UI
renderMedia(allMedia);
};
elements.selectAll.addEventListener('click', () => {
// Select only non-protected media
allMedia.forEach(media => {
if (!protectedMediaIds.has(media.id)) {
selectedMediaIds.add(media.id);
}
});
updateSelectedCount();
renderMedia(allMedia);
});
elements.deselectAll.addEventListener('click', () => {
selectedMediaIds.clear();
updateSelectedCount();
renderMedia(allMedia);
});
elements.applyFilters.addEventListener('click', async () => {
await loadMedia();
});
elements.clearFilters.addEventListener('click', () => {
elements.filterType.value = 'all';
elements.startDate.value = '';
elements.endDate.value = '';
loadMedia();
});
elements.refreshBtn.addEventListener('click', () => {
loadData();
});
elements.deleteSelectedBtn.addEventListener('click', () => {
const count = selectedMediaIds.size;
if (count === 0) return;
elements.modalDeleteCount.textContent = count;
elements.deleteModal.classList.remove('hidden');
});
elements.cancelDeleteBtn.addEventListener('click', () => {
elements.deleteModal.classList.add('hidden');
});
elements.confirmDeleteBtn.addEventListener('click', async () => {
elements.deleteModal.classList.add('hidden');
const mediaIds = Array.from(selectedMediaIds);
showLoading();
try {
const result = await deleteMedia(mediaIds);
const successCount = result.deleted.length;
const failCount = result.failed.length;
let message = `ลบสำเร็จ: ${successCount} media`;
if (failCount > 0) {
message += `\nล้มเหลว: ${failCount} media`;
message += '\n\nรายการที่ล้มเหลว:\n';
result.failed.forEach(f => {
message += `- ${f.id}: ${f.reason}\n`;
});
}
showResult(
failCount === 0 ? 'success' : 'warning',
failCount === 0 ? 'ลบสำเร็จ' : 'ลบเสร็จสิ้น',
message
);
// Clear selection and reload
selectedMediaIds.clear();
updateSelectedCount();
await loadData();
} catch (error) {
hideLoading();
showResult('error', 'เกิดข้อผิดพลาด', error.message);
}
});
elements.closeResultBtn.addEventListener('click', () => {
elements.resultModal.classList.add('hidden');
});
/**
* Modal Functions
*/
function showResult(type, title, message) {
const iconMap = {
success: { bg: 'bg-green-100', icon: 'fas fa-check-circle text-green-500' },
warning: { bg: 'bg-orange-100', icon: 'fas fa-exclamation-triangle text-orange-500' },
error: { bg: 'bg-red-100', icon: 'fas fa-times-circle text-red-500' },
};
const config = iconMap[type] || iconMap.success;
elements.resultIcon.className = `w-12 h-12 ${config.bg} rounded-full flex items-center justify-center`;
elements.resultIcon.querySelector('i').className = `${config.icon} text-xl`;
elements.resultTitle.textContent = title;
elements.resultMessage.innerHTML = message.replace(/\n/g, '<br>');
elements.resultModal.classList.remove('hidden');
}
/**
* Data Loading
*/
async function loadStats() {
try {
const stats = await fetchStats();
updateStats(stats);
} catch (error) {
console.error('Error loading stats:', error);
}
}
async function loadProtectedIds() {
try {
const data = await fetchProtectedMediaIds();
protectedMediaIds = new Set(data.protectedMediaIds || []);
} catch (error) {
console.error('Error loading protected IDs:', error);
}
}
async function loadMedia() {
showLoading();
try {
const type = elements.filterType.value;
const startDate = elements.startDate.value;
const endDate = elements.endDate.value;
const data = await fetchMedia(type, startDate, endDate);
allMedia = data.items || [];
renderMedia(allMedia);
} catch (error) {
console.error('Error loading media:', error);
showResult('error', 'เกิดข้อผิดพลาด', 'ไม่สามารถโหลด media ได้: ' + error.message);
} finally {
hideLoading();
}
}
async function loadData() {
await loadStats();
await loadProtectedIds();
await loadMedia();
}
/**
* Initialize
*/
document.addEventListener('DOMContentLoaded', () => {
// Set default end date to today
const today = new Date().toISOString().split('T')[0];
elements.endDate.value = today;
// Load initial data
loadData();
});