537 lines
33 KiB
HTML
537 lines
33 KiB
HTML
@include(snippets/header)
|
|
<div class="pagewrapper">
|
|
<div id="main" class="admin-container">
|
|
<div class="container">
|
|
<h2>Custom Emojis & Sticker Packs</h2>
|
|
|
|
<!-- Telegram Pack Import -->
|
|
<div class="admin-form-container sp-import-card"
|
|
style="margin-bottom: 28px; text-align: left; background: var(--dropdown-bg); padding: 20px; border: 1px solid var(--nav-border-color); border-radius: 10px;">
|
|
<h4 style="margin: 0 0 6px; font-size: 1rem; display: flex; align-items: center; gap: 8px;">
|
|
<i class="fa-brands fa-telegram" style="color: #26a5e4;"></i>
|
|
Import Telegram Sticker Pack
|
|
</h4>
|
|
<p style="font-size: 0.82em; color: rgba(255,255,255,0.5); margin: 0 0 14px;">
|
|
Enter a Telegram sticker pack URL (e.g. <code>https://t.me/addstickers/PackName</code>) or just the short name.
|
|
All stickers will be downloaded and added as a new sticker pack.
|
|
</p>
|
|
<div style="display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end;">
|
|
<div style="flex: 1; min-width: 200px;">
|
|
<label style="display: block; font-size: 0.8em; margin-bottom: 5px; opacity: 0.7;">Pack URL or Name</label>
|
|
<input type="text" id="sp-pack-url" placeholder="https://t.me/addstickers/PackName"
|
|
style="width: 100%; background: var(--bg); border: 1px solid var(--black); padding: 7px 10px; color: var(--white); border-radius: 5px; box-sizing: border-box;">
|
|
</div>
|
|
<div>
|
|
<label style="display: block; font-size: 0.8em; margin-bottom: 5px; opacity: 0.7;">Display Name (optional)</label>
|
|
<input type="text" id="sp-display-name" placeholder="My Pack Name"
|
|
style="background: var(--bg); border: 1px solid var(--black); padding: 7px 10px; color: var(--white); border-radius: 5px;">
|
|
</div>
|
|
<button id="sp-import-btn" class="btn-upload"
|
|
style="width: auto; padding: 8px 20px; border: 1px solid var(--nav-border-color); background: #26a5e4; color: #fff; cursor: pointer; border-radius: 5px; font-weight: 700; white-space: nowrap;">
|
|
<i class="fa-solid fa-download"></i> Import Pack
|
|
</button>
|
|
</div>
|
|
<div id="sp-import-status" style="margin-top: 12px; font-size: 0.85em; display: none; padding: 10px 14px; border-radius: 7px;"></div>
|
|
</div>
|
|
|
|
<!-- Existing Sticker Packs -->
|
|
<div id="sp-packs-section" style="margin-bottom: 28px;">
|
|
<h3 style="margin: 0 0 12px; font-size: 0.9rem; text-transform: uppercase; letter-spacing: 0.06em; opacity: 0.6;">Imported Sticker Packs</h3>
|
|
<div id="sp-packs-list" style="display: flex; flex-direction: column; gap: 10px;"></div>
|
|
</div>
|
|
|
|
<!-- Standalone Emojis -->
|
|
<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); border-radius: 8px;">
|
|
<h4 style="margin: 0 0 12px;">Add Standalone Emoji</h4>
|
|
<div style="display: flex; gap: 10px; flex-wrap: wrap; align-items: flex-end;">
|
|
<div>
|
|
<label style="display: block; font-size: 0.8em; margin-bottom: 5px; opacity: 0.7;">Name</label>
|
|
<input type="text" id="emoji-name" placeholder=""
|
|
style="background: var(--bg); border: 1px solid var(--black); padding: 5px; color: var(--white);">
|
|
</div>
|
|
<div>
|
|
<label style="display: block; font-size: 0.8em; margin-bottom: 5px; opacity: 0.7;">Image File</label>
|
|
<input type="file" id="emoji-file"
|
|
style="background: var(--bg); border: 1px solid var(--black); padding: 4px; color: var(--white);">
|
|
</div>
|
|
<button id="add-emoji" class="btn-upload"
|
|
style="width: auto; padding: 7px 20px; border: 1px solid var(--nav-border-color); background: var(--bg); color: var(--white); cursor: pointer;">Add</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="emoji-list" class="emoji-grid"></div>
|
|
</div>
|
|
|
|
<!-- Edit Emoji Modal -->
|
|
<div id="edit-emoji-modal" class="modal-overlay" style="display: none;">
|
|
<div class="modal-content" style="max-width: 460px; 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 Emoji</h3>
|
|
<input type="hidden" id="edit-emoji-id">
|
|
<div style="display: flex; flex-direction: column; gap: 12px; text-align: left;">
|
|
<div style="text-align: center;">
|
|
<img id="edit-emoji-preview" src="" alt="" style="height: 64px; width: 64px; object-fit: contain; border-radius: 4px; background: rgba(0,0,0,0.3);">
|
|
</div>
|
|
<div>
|
|
<label style="display: block; font-size: 0.85em; margin-bottom: 4px; color: rgba(255,255,255,0.7);">Name (lowercase a-z, 0-9, _, - only)</label>
|
|
<input type="text" id="edit-emoji-name" style="width: 100%; background: var(--bg, #111); border: 1px solid var(--black, #000); padding: 8px; color: var(--white); border-radius: 4px; box-sizing: border-box;">
|
|
</div>
|
|
<div>
|
|
<label style="display: block; font-size: 0.85em; margin-bottom: 4px; color: rgba(255,255,255,0.7);">Replace Image — Upload New File</label>
|
|
<input type="file" id="edit-emoji-file" accept="image/*" style="width: 100%; background: var(--bg, #111); border: 1px solid var(--black, #000); padding: 8px; color: var(--white); border-radius: 4px; box-sizing: border-box;">
|
|
</div>
|
|
</div>
|
|
<div class="modal-actions" style="margin-top: 20px; display: flex; justify-content: flex-end; gap: 10px;">
|
|
<button onclick="window.emojiAdmin.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.emojiAdmin.saveEmoji()" class="btn-save" style="padding: 8px 16px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer;">Save Changes</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Rename Pack Modal -->
|
|
<div id="rename-pack-modal" class="modal-overlay" style="display:none;">
|
|
<div class="modal-content" style="max-width:440px;background:var(--dropdown-bg,#222);border:1px solid var(--nav-border-color,#444);border-radius:10px;padding:24px;">
|
|
<h3 style="margin:0 0 18px;border-bottom:1px solid var(--nav-border-color,rgba(255,255,255,0.1));padding-bottom:12px;color:var(--white);font-size:1rem;">Rename Sticker Pack</h3>
|
|
<div style="display:flex;flex-direction:column;gap:14px;">
|
|
<div>
|
|
<label style="display:block;font-size:0.82em;margin-bottom:5px;color:rgba(255,255,255,0.6);">New pack name</label>
|
|
<input type="text" id="rename-pack-input" style="width:100%;background:var(--bg,#111);border:1px solid var(--nav-border-color,#555);padding:9px 11px;color:var(--white);border-radius:5px;box-sizing:border-box;font-size:0.95em;">
|
|
</div>
|
|
<label style="display:flex;align-items:center;gap:9px;cursor:pointer;font-size:0.88em;color:rgba(255,255,255,0.75);">
|
|
<input type="checkbox" id="rename-pack-backfill" style="width:15px;height:15px;accent-color:#26a5e4;cursor:pointer;">
|
|
Also rename all stickers and update comment references
|
|
</label>
|
|
<div id="rename-pack-result" style="display:none;font-size:0.82em;padding:8px 12px;border-radius:5px;background:rgba(40,167,69,0.15);border:1px solid rgba(40,167,69,0.3);color:#4caf50;"></div>
|
|
</div>
|
|
<div style="margin-top:20px;display:flex;justify-content:flex-end;gap:10px;">
|
|
<button id="rename-pack-cancel" style="padding:8px 16px;background:rgba(255,255,255,0.08);color:var(--white);border:1px solid rgba(255,255,255,0.2);border-radius:5px;cursor:pointer;font-size:0.9em;">Cancel</button>
|
|
<button id="rename-pack-submit" style="padding:8px 20px;background:#26a5e4;color:#fff;border:none;border-radius:5px;cursor:pointer;font-weight:700;font-size:0.9em;">Rename</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
(() => {
|
|
var i18n = window.f0ckI18n || {};
|
|
var csrf = '{{ csrf_token }}';
|
|
|
|
function esc(s) {
|
|
return (s || '').toString()
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
/* Returns an HTML string for a sticker preview — <video> for .webm, <img> otherwise */
|
|
function stickerEl(url, name, extraAttrs) {
|
|
var a = extraAttrs || '';
|
|
if (url && url.slice(-5) === '.webm') {
|
|
return '<video class="emoji-preview" src="' + url + '" autoplay loop muted playsinline title=":' + esc(name) + ':"' + a + '></video>';
|
|
}
|
|
return '<img class="emoji-preview" src="' + url + '" alt=":' + esc(name) + ':"' + a + '>';
|
|
}
|
|
|
|
/* ── DATA LOAD ── */
|
|
function loadAll() {
|
|
Promise.all([
|
|
fetch('/api/v2/emojis'),
|
|
fetch('/api/v2/admin/sticker-packs', { headers: { 'X-Requested-With': 'XMLHttpRequest' } })
|
|
]).then(function(responses) {
|
|
return Promise.all([responses[0].json(), responses[1].json()]);
|
|
}).then(function(results) {
|
|
var emojisData = results[0];
|
|
var packsData = results[1];
|
|
window.emojiAdmin.emojis = emojisData.emojis || [];
|
|
window.emojiAdmin.packs = packsData.packs || [];
|
|
renderStandaloneEmojis(window.emojiAdmin.emojis);
|
|
renderPacksList(window.emojiAdmin.packs);
|
|
}).catch(function(err) {
|
|
console.error('[EMOJI_ADMIN] Load Error:', err);
|
|
});
|
|
}
|
|
|
|
/* ── STANDALONE EMOJIS ── */
|
|
function renderStandaloneEmojis(emojis) {
|
|
var grid = document.getElementById('emoji-list');
|
|
if (!grid) return;
|
|
var standalone = emojis.filter(function(e) { return !e.pack_id; });
|
|
if (standalone.length === 0) {
|
|
grid.innerHTML = '<p style="opacity:0.5;font-size:0.85em;">No standalone emojis yet.</p>';
|
|
return;
|
|
}
|
|
grid.innerHTML = standalone.map(function(e) {
|
|
return '<div class="emoji-card">' +
|
|
'<button class="emoji-delete" onclick="window.emojiAdmin.deleteEmoji(' + e.id + ')" title="Delete">x</button>' +
|
|
stickerEl(e.url, e.name) +
|
|
'<span class="emoji-label">:' + esc(e.name) + ':</span>' +
|
|
'<button onclick="window.emojiAdmin.openEditModal(' + e.id + ')" style="margin-top:6px;width:100%;padding:4px 0;font-size:0.75em;background:#28a745;color:white;border:none;border-radius:3px;cursor:pointer;">Edit</button>' +
|
|
'</div>';
|
|
}).join('');
|
|
}
|
|
|
|
/* ── STICKER PACKS LIST ── */
|
|
function renderPacksList(packs) {
|
|
var container = document.getElementById('sp-packs-list');
|
|
if (!container) return;
|
|
if (packs.length === 0) {
|
|
container.innerHTML = '<p style="opacity:0.5;font-size:0.85em;">No sticker packs imported yet.</p>';
|
|
return;
|
|
}
|
|
container.innerHTML = packs.map(function(p) {
|
|
var tgLink = p.tg_name
|
|
? '<a href="https://t.me/addstickers/' + esc(p.tg_name) + '" target="_blank" rel="noopener" style="font-size:0.72em;color:#26a5e4;text-decoration:none;opacity:0.8;">@' + esc(p.tg_name) + ' ↗</a>'
|
|
: '';
|
|
var count = (p.actual_count !== undefined && p.actual_count !== null) ? p.actual_count : (p.sticker_count || 0);
|
|
return '<div class="sp-pack-row" data-pack-id="' + p.id + '" style="background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.1);border-radius:10px;padding:14px 16px;">' +
|
|
'<div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;">' +
|
|
'<div style="flex:1;min-width:0;">' +
|
|
'<div style="display:flex;align-items:center;gap:8px;flex-wrap:wrap;">' +
|
|
'<span class="sp-pack-name" style="font-size:0.95rem;font-weight:700;">' + esc(p.name) + '</span>' +
|
|
tgLink +
|
|
'</div>' +
|
|
'<div style="font-size:0.76em;opacity:0.5;margin-top:2px;">' + count + ' sticker(s)</div>' +
|
|
'</div>' +
|
|
'<div style="display:flex;gap:8px;flex-shrink:0;">' +
|
|
'<button onclick="window.emojiAdmin.renamePack(' + p.id + ')" style="padding:5px 12px;border-radius:5px;border:1px solid rgba(255,255,255,0.18);background:rgba(255,255,255,0.07);color:#fff;cursor:pointer;font-size:0.8em;">Rename</button>' +
|
|
'<button onclick="window.emojiAdmin.togglePackEmojis(' + p.id + ')" style="padding:5px 12px;border-radius:5px;border:1px solid rgba(255,255,255,0.18);background:rgba(255,255,255,0.07);color:#fff;cursor:pointer;font-size:0.8em;">View</button>' +
|
|
'<button onclick="window.emojiAdmin.deletePack(' + p.id + ')" style="padding:5px 12px;border-radius:5px;border:1px solid rgba(180,40,40,0.5);background:rgba(180,40,40,0.15);color:#e55;cursor:pointer;font-size:0.8em;">Delete</button>' +
|
|
'</div>' +
|
|
'</div>' +
|
|
'<div id="sp-emojis-' + p.id + '" style="display:none;margin-top:12px;"></div>' +
|
|
'</div>';
|
|
}).join('');
|
|
}
|
|
|
|
function renderPackGrid(packId) {
|
|
var container = document.getElementById('sp-emojis-' + packId);
|
|
if (!container) return;
|
|
// Preserve scroll position of the inner grid div
|
|
var prevScroll = (container.querySelector('.emoji-grid') || {}).scrollTop || 0;
|
|
var pack = (window.emojiAdmin.packs || []).find(function(p) { return p.id === packId; });
|
|
var currentThumb = pack ? (pack.thumb_url || '') : '';
|
|
var packEmojis = (window.emojiAdmin.emojis || []).filter(function(e) { return e.pack_id === packId; });
|
|
if (packEmojis.length === 0) {
|
|
container.innerHTML = '<p style="opacity:0.5;font-size:0.82em;padding:8px 0;">No stickers.</p>';
|
|
} else {
|
|
container.innerHTML = '<div class="emoji-grid" style="max-height:320px;overflow-y:auto;">' +
|
|
packEmojis.map(function(e) {
|
|
var isCurrent = currentThumb && e.url === currentThumb;
|
|
var btnBg = isCurrent ? 'rgba(40,167,69,0.18)' : 'rgba(255,200,0,0.15)';
|
|
var btnClr = isCurrent ? '#4caf50' : '#ffd700';
|
|
var btnBdr = isCurrent ? 'rgba(40,167,69,0.35)' : 'rgba(255,200,0,0.3)';
|
|
var btnLbl = isCurrent ? '\u2713 Current Icon' : '\u2B50 Set as icon';
|
|
return '<div class="emoji-card" style="position:relative;">' +
|
|
'<button class="emoji-delete" onclick="window.emojiAdmin.deleteEmoji(' + e.id + ')" title="Delete">x</button>' +
|
|
stickerEl(e.url, e.name, ' loading="lazy"') +
|
|
'<span class="emoji-label">:' + esc(e.name) + ':</span>' +
|
|
'<button onclick="window.emojiAdmin.setPackThumb(' + packId + ',' + e.id + ')"' +
|
|
' title="Use as tab icon" style="margin-top:5px;width:100%;padding:3px 0;font-size:0.72em;background:' + btnBg + ';color:' + btnClr + ';border:1px solid ' + btnBdr + ';border-radius:3px;cursor:pointer;">' + btnLbl + '</button>' +
|
|
'</div>';
|
|
}).join('') +
|
|
'</div>';
|
|
// Restore scroll position
|
|
var newGrid = container.querySelector('.emoji-grid');
|
|
if (newGrid && prevScroll) newGrid.scrollTop = prevScroll;
|
|
}
|
|
container.dataset.loaded = '1';
|
|
}
|
|
|
|
function togglePackEmojis(packId) {
|
|
var container = document.getElementById('sp-emojis-' + packId);
|
|
if (!container) return;
|
|
if (container.style.display !== 'none') { container.style.display = 'none'; return; }
|
|
container.style.display = 'block';
|
|
renderPackGrid(packId);
|
|
}
|
|
|
|
/* ── STANDALONE EMOJI ACTIONS ── */
|
|
function addEmoji(e) {
|
|
if (e) e.preventDefault();
|
|
var name = document.getElementById('emoji-name').value;
|
|
var fileInput = document.getElementById('emoji-file');
|
|
if (!name || !fileInput.files[0]) return alert('Fill Name and select a File');
|
|
|
|
var btn = document.getElementById('add-emoji');
|
|
var oldText = btn.textContent;
|
|
btn.disabled = true;
|
|
btn.textContent = i18n.uploading || 'Uploading...';
|
|
|
|
var formData = new FormData();
|
|
formData.append('name', name);
|
|
formData.append('file', fileInput.files[0]);
|
|
|
|
var headers = { 'X-Requested-With': 'XMLHttpRequest' };
|
|
if (csrf) headers['X-CSRF-Token'] = csrf;
|
|
|
|
fetch('/api/v2/admin/emojis', { method: 'POST', headers: headers, body: formData })
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (data.success) {
|
|
document.getElementById('emoji-name').value = '';
|
|
document.getElementById('emoji-file').value = '';
|
|
loadAll();
|
|
} else {
|
|
alert('Failed: ' + (data.message || data.msg || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(function(err) { alert('Error: ' + err.message); })
|
|
.finally(function() { btn.disabled = false; btn.textContent = oldText; });
|
|
}
|
|
|
|
function deleteEmoji(id) {
|
|
if (!confirm('Delete this emoji?')) return;
|
|
fetch('/api/v2/admin/emojis/' + id + '/delete', { method: 'POST', headers: { 'X-CSRF-Token': csrf } })
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) { if (data.success) loadAll(); else alert('Delete failed'); })
|
|
.catch(function(err) { console.error(err); });
|
|
}
|
|
|
|
function openEditModal(id) {
|
|
var emoji = (window.emojiAdmin.emojis || []).find(function(e) { return e.id === id; });
|
|
if (!emoji) return;
|
|
document.getElementById('edit-emoji-id').value = emoji.id;
|
|
document.getElementById('edit-emoji-name').value = emoji.name;
|
|
document.getElementById('edit-emoji-file').value = '';
|
|
var preview = document.getElementById('edit-emoji-preview');
|
|
preview.src = emoji.url;
|
|
preview.alt = ':' + emoji.name + ':';
|
|
var modal = document.getElementById('edit-emoji-modal');
|
|
if (modal) modal.style.display = 'flex';
|
|
}
|
|
|
|
function closeEditModal() {
|
|
var modal = document.getElementById('edit-emoji-modal');
|
|
if (modal) modal.style.display = 'none';
|
|
document.getElementById('edit-emoji-file').value = '';
|
|
}
|
|
|
|
function saveEmoji() {
|
|
var id = document.getElementById('edit-emoji-id').value;
|
|
var name = document.getElementById('edit-emoji-name').value.trim().toLowerCase();
|
|
var fileInput = document.getElementById('edit-emoji-file');
|
|
|
|
if (!name) return alert('Emoji name is required');
|
|
if (!/^[a-z0-9_-]+$/.test(name)) return alert('Invalid name. Use lowercase a-z, 0-9, _, - only.');
|
|
|
|
var btn = document.querySelector('#edit-emoji-modal .btn-save');
|
|
var oldText = btn.textContent;
|
|
btn.disabled = true;
|
|
btn.textContent = 'Saving...';
|
|
|
|
var formData = new FormData();
|
|
formData.append('name', name);
|
|
if (fileInput.files[0]) formData.append('file', fileInput.files[0]);
|
|
|
|
var headers = { 'X-Requested-With': 'XMLHttpRequest' };
|
|
if (csrf) headers['X-CSRF-Token'] = csrf;
|
|
|
|
fetch('/api/v2/admin/emojis/' + id + '/edit', { method: 'POST', headers: headers, body: formData })
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (data.success) { closeEditModal(); loadAll(); }
|
|
else alert('Save failed: ' + (data.message || data.msg || 'Unknown error'));
|
|
})
|
|
.catch(function(err) { alert('Save failed: ' + err.message); })
|
|
.finally(function() { btn.disabled = false; btn.textContent = oldText; });
|
|
}
|
|
|
|
/* ── STICKER PACK ACTIONS ── */
|
|
function importPack() {
|
|
var packUrl = (document.getElementById('sp-pack-url').value || '').trim();
|
|
var displayName = (document.getElementById('sp-display-name').value || '').trim();
|
|
var statusEl = document.getElementById('sp-import-status');
|
|
var btn = document.getElementById('sp-import-btn');
|
|
|
|
if (!packUrl) return alert('Please enter a sticker pack URL or name.');
|
|
|
|
btn.disabled = true;
|
|
btn.textContent = 'Importing...';
|
|
statusEl.style.display = 'none';
|
|
|
|
fetch('/api/v2/admin/sticker-packs/import', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-Token': csrf,
|
|
'X-Requested-With': 'XMLHttpRequest'
|
|
},
|
|
body: JSON.stringify({ pack_name: packUrl, display_name: displayName })
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
statusEl.style.display = 'block';
|
|
if (data.success) {
|
|
statusEl.style.background = 'rgba(40,167,69,0.15)';
|
|
statusEl.style.border = '1px solid rgba(40,167,69,0.4)';
|
|
statusEl.style.color = '#4caf50';
|
|
var packName = (data.pack && data.pack.name) ? data.pack.name : packUrl;
|
|
statusEl.innerHTML = 'Imported "' + esc(packName) + '" — ' + (data.imported || 0) + ' sticker(s) added' + (data.failed ? ', ' + data.failed + ' failed' : '') + '.';
|
|
document.getElementById('sp-pack-url').value = '';
|
|
document.getElementById('sp-display-name').value = '';
|
|
loadAll();
|
|
} else {
|
|
statusEl.style.background = 'rgba(180,40,40,0.15)';
|
|
statusEl.style.border = '1px solid rgba(180,40,40,0.4)';
|
|
statusEl.style.color = '#e55';
|
|
statusEl.textContent = data.message || 'Import failed';
|
|
}
|
|
})
|
|
.catch(function(err) {
|
|
statusEl.style.display = 'block';
|
|
statusEl.style.background = 'rgba(180,40,40,0.15)';
|
|
statusEl.style.border = '1px solid rgba(180,40,40,0.4)';
|
|
statusEl.style.color = '#e55';
|
|
statusEl.textContent = 'Error: ' + err.message;
|
|
})
|
|
.finally(function() {
|
|
btn.disabled = false;
|
|
btn.innerHTML = '<i class="fa-solid fa-download"></i> Import Pack';
|
|
});
|
|
}
|
|
|
|
function deletePack(id) {
|
|
if (!confirm('Delete this sticker pack AND all its stickers? This cannot be undone.')) return;
|
|
fetch('/api/v2/admin/sticker-packs/' + id, {
|
|
method: 'DELETE',
|
|
headers: { 'X-CSRF-Token': csrf, 'X-Requested-With': 'XMLHttpRequest' }
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) { if (data.success) loadAll(); else alert('Delete failed: ' + (data.message || 'Unknown error')); })
|
|
.catch(function(err) { alert('Error: ' + err.message); });
|
|
}
|
|
|
|
var _renamePendingId = null;
|
|
var _renamePendingCurrentName = null;
|
|
|
|
function renamePack(id) {
|
|
var pack = (window.emojiAdmin.packs || []).find(function(p) { return p.id === id; });
|
|
_renamePendingId = id;
|
|
_renamePendingCurrentName = pack ? pack.name : '';
|
|
|
|
document.getElementById('rename-pack-input').value = _renamePendingCurrentName;
|
|
document.getElementById('rename-pack-backfill').checked = false;
|
|
document.getElementById('rename-pack-result').style.display = 'none';
|
|
document.getElementById('rename-pack-result').textContent = '';
|
|
document.getElementById('rename-pack-submit').disabled = false;
|
|
document.getElementById('rename-pack-submit').textContent = 'Rename';
|
|
document.getElementById('rename-pack-modal').style.display = 'flex';
|
|
setTimeout(function() { document.getElementById('rename-pack-input').focus(); }, 50);
|
|
}
|
|
|
|
document.getElementById('rename-pack-cancel').addEventListener('click', function() {
|
|
document.getElementById('rename-pack-modal').style.display = 'none';
|
|
});
|
|
|
|
document.getElementById('rename-pack-submit').addEventListener('click', function() {
|
|
var newName = (document.getElementById('rename-pack-input').value || '').trim();
|
|
var backfill = document.getElementById('rename-pack-backfill').checked;
|
|
if (!newName) return;
|
|
|
|
// Allow same name when backfill is requested
|
|
if (newName === _renamePendingCurrentName && !backfill) {
|
|
document.getElementById('rename-pack-modal').style.display = 'none';
|
|
return;
|
|
}
|
|
|
|
var btn = document.getElementById('rename-pack-submit');
|
|
btn.disabled = true;
|
|
btn.textContent = 'Renaming...';
|
|
|
|
fetch('/api/v2/admin/sticker-packs/' + _renamePendingId + '/rename', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf, 'X-Requested-With': 'XMLHttpRequest' },
|
|
body: JSON.stringify({ name: newName, backfill: backfill })
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (data.success) {
|
|
if (backfill) {
|
|
var res = document.getElementById('rename-pack-result');
|
|
res.textContent = (data.emojiRenamed || 0) + ' sticker(s) renamed, ' + (data.commentRowsUpdated || 0) + ' comment row(s) updated.';
|
|
res.style.display = 'block';
|
|
setTimeout(function() {
|
|
document.getElementById('rename-pack-modal').style.display = 'none';
|
|
loadAll();
|
|
}, 2000);
|
|
} else {
|
|
document.getElementById('rename-pack-modal').style.display = 'none';
|
|
loadAll();
|
|
}
|
|
} else {
|
|
var res = document.getElementById('rename-pack-result');
|
|
res.textContent = 'Rename failed: ' + (data.message || 'Unknown error');
|
|
res.style.background = 'rgba(220,53,69,0.15)';
|
|
res.style.borderColor = 'rgba(220,53,69,0.3)';
|
|
res.style.color = '#e55';
|
|
res.style.display = 'block';
|
|
btn.disabled = false;
|
|
btn.textContent = 'Rename';
|
|
}
|
|
})
|
|
.catch(function(err) {
|
|
var res = document.getElementById('rename-pack-result');
|
|
res.textContent = 'Error: ' + err.message;
|
|
res.style.display = 'block';
|
|
btn.disabled = false;
|
|
btn.textContent = 'Rename';
|
|
});
|
|
});
|
|
|
|
// Close modal on overlay click
|
|
document.getElementById('rename-pack-modal').addEventListener('click', function(e) {
|
|
if (e.target === this) this.style.display = 'none';
|
|
});
|
|
|
|
function setPackThumb(packId, emojiId) {
|
|
var emoji = (window.emojiAdmin.emojis || []).find(function(e) { return e.id === emojiId; });
|
|
if (!emoji || !emoji.url) return alert('Could not find emoji URL.');
|
|
var thumbUrl = emoji.url;
|
|
fetch('/api/v2/admin/sticker-packs/' + packId + '/thumb', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf, 'X-Requested-With': 'XMLHttpRequest' },
|
|
body: JSON.stringify({ thumb_url: thumbUrl })
|
|
})
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (data.success) {
|
|
// Use the URL the server actually stored (may be a generated WebP for .webm stickers)
|
|
var pack = (window.emojiAdmin.packs || []).find(function(p) { return p.id === packId; });
|
|
if (pack) pack.thumb_url = data.thumb_url || thumbUrl;
|
|
// Re-render only this pack's grid — grid stays open
|
|
renderPackGrid(packId);
|
|
} else {
|
|
alert('Failed to set icon: ' + (data.message || 'Unknown error'));
|
|
}
|
|
})
|
|
.catch(function(err) { alert('Error: ' + err.message); });
|
|
}
|
|
|
|
// Global scope for onclick handlers
|
|
window.emojiAdmin = {
|
|
deleteEmoji: deleteEmoji,
|
|
openEditModal: openEditModal,
|
|
closeEditModal: closeEditModal,
|
|
saveEmoji: saveEmoji,
|
|
importPack: importPack,
|
|
deletePack: deletePack,
|
|
renamePack: renamePack,
|
|
setPackThumb: setPackThumb,
|
|
togglePackEmojis: togglePackEmojis,
|
|
emojis: [],
|
|
packs: []
|
|
};
|
|
|
|
document.getElementById('add-emoji').addEventListener('click', addEmoji);
|
|
document.getElementById('sp-import-btn').addEventListener('click', importPack);
|
|
|
|
// Reload when SSE emits emojis_updated
|
|
document.addEventListener('f0ck:emojis_updated', loadAll);
|
|
|
|
loadAll();
|
|
})();
|
|
</script>
|
|
</div>
|
|
</div>
|
|
@include(snippets/footer) |