286 lines
16 KiB
HTML
286 lines
16 KiB
HTML
@include(snippets/header)
|
|
<div class="pagewrapper">
|
|
<div id="main" class="admin-container">
|
|
<div class="container">
|
|
<h2>Meme Manager</h2>
|
|
|
|
<div class="admin-form-container"
|
|
style="margin-bottom: 20px; text-align: left; background: var(--dropdown-bg); padding: 15px; border: 1px solid var(--nav-border-color);">
|
|
<h4>Add New Meme Template</h4>
|
|
<div style="display: flex; flex-direction: column; gap: 10px; max-width: 500px;">
|
|
<input type="text" id="meme-id" placeholder="Template ID (e.g. surprised-pikachu)" style="background: var(--bg); border: 1px solid var(--black); padding: 8px; color: var(--white);">
|
|
<input type="text" id="meme-name" placeholder="Display Name (e.g. Surprised Pikachu)" style="background: var(--bg); border: 1px solid var(--black); padding: 8px; color: var(--white);">
|
|
<input type="text" id="meme-url" placeholder="Image URL (Alternative to upload)" style="background: var(--bg); border: 1px solid var(--black); padding: 8px; color: var(--white);">
|
|
<div style="margin: 5px 0; color: #888; font-size: 0.8em; text-align: center;">- OR -</div>
|
|
<input type="file" id="meme-file" accept="image/*" style="background: var(--bg); border: 1px solid var(--black); padding: 8px; color: var(--white);">
|
|
<input type="text" id="meme-category" placeholder="Category (e.g. Classic, Reaction)" style="background: var(--bg); border: 1px solid var(--black); padding: 8px; color: var(--white);">
|
|
<button id="add-meme" class="btn-upload" style="width: auto; padding: 10px 20px; border: 1px solid var(--nav-border-color); background: var(--bg); color: var(--white); cursor: pointer; align-self: flex-start;">Add Template</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="admin-table-container" style="overflow-x: auto;">
|
|
<table style="width: 100%; border-collapse: collapse; color: var(--white);">
|
|
<thead>
|
|
<tr style="border-bottom: 1px solid var(--nav-border-color); text-align: left;">
|
|
<th style="padding: 10px;">Preview</th>
|
|
<th style="padding: 10px;">Name</th>
|
|
<th style="padding: 10px;">Category</th>
|
|
<th style="padding: 10px;">Template ID</th>
|
|
<th style="padding: 10px;">Actions</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="meme-list">
|
|
<!-- Populated by JS -->
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
|
|
<!-- Edit Meme Modal -->
|
|
<div id="edit-meme-modal" class="modal-overlay" style="display: none;">
|
|
<div class="modal-content" style="max-width: 500px; background: var(--dropdown-bg, #222); border: 1px solid var(--nav-border-color, #444); border-radius: 8px; padding: 20px;">
|
|
<h3 style="margin-top: 0; margin-bottom: 15px; border-bottom: 1px solid var(--nav-border-color, rgba(255,255,255,0.1)); padding-bottom: 10px; color: var(--white);">Edit Meme Template</h3>
|
|
|
|
<input type="hidden" id="edit-meme-id-db">
|
|
|
|
<div style="display: flex; flex-direction: column; gap: 12px; text-align: left;">
|
|
<div>
|
|
<label style="display: block; font-size: 0.85em; margin-bottom: 4px; color: rgba(255,255,255,0.7);">Template ID (lowercase a-z, 0-9, - only)</label>
|
|
<input type="text" id="edit-meme-template-id" style="width: 100%; background: var(--bg, #111); border: 1px solid var(--black, #000); padding: 8px; color: var(--white); border-radius: 4px;">
|
|
</div>
|
|
|
|
<div>
|
|
<label style="display: block; font-size: 0.85em; margin-bottom: 4px; color: rgba(255,255,255,0.7);">Display Name</label>
|
|
<input type="text" id="edit-meme-name" style="width: 100%; background: var(--bg, #111); border: 1px solid var(--black, #000); padding: 8px; color: var(--white); border-radius: 4px;">
|
|
</div>
|
|
|
|
<div>
|
|
<label style="display: block; font-size: 0.85em; margin-bottom: 4px; color: rgba(255,255,255,0.7);">Category</label>
|
|
<input type="text" id="edit-meme-category" style="width: 100%; background: var(--bg, #111); border: 1px solid var(--black, #000); padding: 8px; color: var(--white); border-radius: 4px;">
|
|
</div>
|
|
|
|
<div>
|
|
<label style="display: block; font-size: 0.85em; margin-bottom: 4px; color: rgba(255,255,255,0.7);">Image URL</label>
|
|
<input type="text" id="edit-meme-url" style="width: 100%; background: var(--bg, #111); border: 1px solid var(--black, #000); padding: 8px; color: var(--white); border-radius: 4px;">
|
|
</div>
|
|
|
|
<div style="text-align: center; margin: 4px 0; font-size: 0.8em; opacity: 0.5; color: var(--white);">- OR -</div>
|
|
|
|
<div>
|
|
<label style="display: block; font-size: 0.85em; margin-bottom: 4px; color: rgba(255,255,255,0.7);">Upload New Image File</label>
|
|
<input type="file" id="edit-meme-file" accept="image/*" style="width: 100%; background: var(--bg, #111); border: 1px solid var(--black, #000); padding: 8px; color: var(--white); border-radius: 4px;">
|
|
</div>
|
|
</div>
|
|
|
|
<div class="modal-actions" style="margin-top: 20px; display: flex; justify-content: flex-end; gap: 10px;">
|
|
<button onclick="window.memeAdmin.closeEditModal()" class="btn-cancel" style="padding: 8px 16px; background: rgba(255,255,255,0.1); color: var(--white); border: 1px solid rgba(255,255,255,0.2); border-radius: 4px; cursor: pointer;">Cancel</button>
|
|
<button onclick="window.memeAdmin.saveMeme()" class="btn-save" style="padding: 8px 16px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer;">Save Changes</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
window.f0ckDebug = window.f0ckDebug || (() => {});
|
|
(() => {
|
|
var i18n = window.f0ckI18n || {};
|
|
window.f0ckDebug('[MEME_ADMIN] Initializing');
|
|
const esc = (s) => (s || '').toString().replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
const loadMemes = async () => {
|
|
try {
|
|
const res = await fetch('/api/v2/memes');
|
|
const data = await res.json();
|
|
if (data.success) {
|
|
window.memeAdmin.memes = data.memes;
|
|
const tbody = document.getElementById('meme-list');
|
|
if (!tbody) return;
|
|
tbody.innerHTML = data.memes.map(m =>
|
|
'<tr style="border-bottom: 1px solid rgba(255,255,255,0.05);">' +
|
|
'<td style="padding: 10px;"><img src="' + m.url + '" style="height: 60px; width: 60px; object-fit: contain; border-radius: 4px; background: #000;"></td>' +
|
|
'<td style="padding: 10px;">' + esc(m.name) + '</td>' +
|
|
'<td style="padding: 10px;"><span style="background: rgba(255,255,255,0.1); padding: 2px 8px; border-radius: 10px; font-size: 0.85em;">' + esc(m.category || 'General') + '</span></td>' +
|
|
'<td style="padding: 10px; font-family: monospace; opacity: 0.7;">' + esc(m.template_id) + '</td>' +
|
|
'<td style="padding: 10px;">' +
|
|
'<button onclick="window.memeAdmin.openEditModal(' + m.id + ')" class="btn-edit" style="padding: 5px 15px; font-size: 0.8em; background: #28a745; color: white; border: none; cursor: pointer; border-radius: 2px; margin-right: 5px;">Edit</button>' +
|
|
'<button onclick="window.memeAdmin.deleteMeme(' + m.id + ')" class="btn-remove" style="padding: 5px 15px; font-size: 0.8em; background: #c00; color: white; border: none; cursor: pointer; border-radius: 2px;">Delete</button>' +
|
|
'</td>' +
|
|
'</tr>'
|
|
).join('');
|
|
}
|
|
} catch (err) { console.error('[MEME_ADMIN] Load Error:', err); }
|
|
};
|
|
|
|
const addMeme = async (e) => {
|
|
if (e) e.preventDefault();
|
|
window.f0ckDebug('[MEME_ADMIN] addMeme triggered');
|
|
|
|
const template_id = document.getElementById('meme-id').value;
|
|
const name = document.getElementById('meme-name').value;
|
|
const url = document.getElementById('meme-url').value;
|
|
const category = document.getElementById('meme-category').value;
|
|
const fileInput = document.getElementById('meme-file');
|
|
|
|
if (!template_id || !name || (!url && !fileInput.files[0])) {
|
|
return alert('Fill all fields (ID, Name, and either URL or File)');
|
|
}
|
|
|
|
const btn = document.getElementById('add-meme');
|
|
const oldText = btn.textContent;
|
|
btn.disabled = true;
|
|
btn.textContent = i18n.uploading || 'Uploading...';
|
|
|
|
const formData = new FormData();
|
|
formData.append('template_id', template_id);
|
|
formData.append('name', name);
|
|
formData.append('url', url);
|
|
formData.append('category', category);
|
|
if (fileInput.files[0]) {
|
|
formData.append('file', fileInput.files[0]);
|
|
}
|
|
|
|
try {
|
|
const headers = {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
};
|
|
const csrf = '{{ csrf_token }}';
|
|
if (csrf) headers['X-CSRF-Token'] = csrf;
|
|
|
|
const res = await fetch('/api/v2/admin/memes', {
|
|
method: 'POST',
|
|
headers: headers,
|
|
body: formData
|
|
});
|
|
|
|
const data = await res.json();
|
|
if (data.success) {
|
|
document.getElementById('meme-id').value = '';
|
|
document.getElementById('meme-name').value = '';
|
|
document.getElementById('meme-url').value = '';
|
|
document.getElementById('meme-category').value = '';
|
|
document.getElementById('meme-file').value = '';
|
|
loadMemes();
|
|
} else {
|
|
alert('Server Error: ' + (data.message || data.msg || 'Unknown error'));
|
|
}
|
|
} catch (e) {
|
|
console.error('[MEME_ADMIN] Post Error:', e);
|
|
alert('Submission failed: ' + e.message);
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.textContent = oldText;
|
|
}
|
|
};
|
|
|
|
const deleteMeme = async (id) => {
|
|
if (!confirm('Delete this template?')) return;
|
|
try {
|
|
const res = await fetch('/api/v2/admin/memes/' + id + '/delete', {
|
|
method: 'POST',
|
|
headers: { 'X-CSRF-Token': '{{ csrf_token }}' }
|
|
});
|
|
const data = await res.json();
|
|
if (data.success) {
|
|
loadMemes();
|
|
} else {
|
|
alert('Delete failed');
|
|
}
|
|
} catch (e) { console.error(e); }
|
|
};
|
|
|
|
const openEditModal = (id) => {
|
|
const meme = (window.memeAdmin.memes || []).find(m => m.id === id);
|
|
if (!meme) return;
|
|
|
|
document.getElementById('edit-meme-id-db').value = meme.id;
|
|
document.getElementById('edit-meme-template-id').value = meme.template_id;
|
|
document.getElementById('edit-meme-name').value = meme.name;
|
|
document.getElementById('edit-meme-category').value = meme.category || 'General';
|
|
document.getElementById('edit-meme-url').value = meme.url;
|
|
document.getElementById('edit-meme-file').value = '';
|
|
|
|
const modal = document.getElementById('edit-meme-modal');
|
|
if (modal) modal.style.display = 'flex';
|
|
};
|
|
|
|
const closeEditModal = () => {
|
|
const modal = document.getElementById('edit-meme-modal');
|
|
if (modal) modal.style.display = 'none';
|
|
document.getElementById('edit-meme-file').value = '';
|
|
};
|
|
|
|
const saveMeme = async () => {
|
|
const id = document.getElementById('edit-meme-id-db').value;
|
|
const template_id = document.getElementById('edit-meme-template-id').value.trim().toLowerCase();
|
|
const name = document.getElementById('edit-meme-name').value.trim();
|
|
const category = document.getElementById('edit-meme-category').value.trim() || 'General';
|
|
const url = document.getElementById('edit-meme-url').value.trim();
|
|
const fileInput = document.getElementById('edit-meme-file');
|
|
|
|
if (!template_id || !name) {
|
|
return alert('Template ID and Display Name are required');
|
|
}
|
|
|
|
if (!/^[a-z0-9-]+$/.test(template_id)) {
|
|
return alert('Invalid ID. Use lowercase a-z, 0-9, - only.');
|
|
}
|
|
|
|
const btn = document.querySelector('#edit-meme-modal .btn-save');
|
|
const oldText = btn.textContent;
|
|
btn.disabled = true;
|
|
btn.textContent = 'Saving...';
|
|
|
|
const formData = new FormData();
|
|
formData.append('template_id', template_id);
|
|
formData.append('name', name);
|
|
formData.append('category', category);
|
|
formData.append('url', url);
|
|
if (fileInput.files[0]) {
|
|
formData.append('file', fileInput.files[0]);
|
|
}
|
|
|
|
try {
|
|
const headers = {
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
};
|
|
const csrf = '{{ csrf_token }}';
|
|
if (csrf) headers['X-CSRF-Token'] = csrf;
|
|
|
|
const res = await fetch('/api/v2/admin/memes/' + id + '/edit', {
|
|
method: 'POST',
|
|
headers: headers,
|
|
body: formData
|
|
});
|
|
|
|
const data = await res.json();
|
|
if (data.success) {
|
|
closeEditModal();
|
|
loadMemes();
|
|
} else {
|
|
alert('Server Error: ' + (data.message || 'Unknown error'));
|
|
}
|
|
} catch (e) {
|
|
console.error('[MEME_ADMIN] Edit Error:', e);
|
|
alert('Save failed: ' + e.message);
|
|
} finally {
|
|
btn.disabled = false;
|
|
btn.textContent = oldText;
|
|
}
|
|
};
|
|
|
|
// Global scope for onclick handlers
|
|
window.memeAdmin = { deleteMeme, openEditModal, closeEditModal, saveMeme, memes: [] };
|
|
|
|
const btnAddMeme = document.getElementById('add-meme');
|
|
if (btnAddMeme) {
|
|
window.f0ckDebug('[MEME_ADMIN] Registering click listener');
|
|
btnAddMeme.addEventListener('click', addMeme);
|
|
} else {
|
|
console.error('[MEME_ADMIN] Add button not found!');
|
|
}
|
|
|
|
loadMemes();
|
|
})();
|
|
</script>
|
|
</div>
|
|
</div>
|
|
@include(snippets/footer)
|