Files
f0ckm/views/admin/emojis.html

537 lines
33 KiB
HTML

@include(snippets/header)
<div class="pagewrapper">
<div id="main" class="admin-container">
<div class="container">
<h2>Custom Emojis &amp; 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 &mdash; 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, '&amp;')
.replace(/</g, '&lt;')
.replace(/>/g, '&gt;')
.replace(/"/g, '&quot;')
.replace(/'/g, '&#039;');
}
/* 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) + ' &nearr;</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) + '" &mdash; ' + (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)