telegram stickers v1
This commit is contained in:
@@ -2,54 +2,85 @@
|
||||
<div class="pagewrapper">
|
||||
<div id="main" class="admin-container">
|
||||
<div class="container">
|
||||
<h2>Custom Emojis</h2>
|
||||
<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);">
|
||||
<h4>Add New Emoji</h4>
|
||||
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);">
|
||||
<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);">
|
||||
<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>
|
||||
<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">
|
||||
<!-- Populated by JS -->
|
||||
</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>
|
||||
<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>
|
||||
@@ -60,168 +91,313 @@
|
||||
<script>
|
||||
(() => {
|
||||
var i18n = window.f0ckI18n || {};
|
||||
const esc = (s) => (s || '').toString().replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
||||
var csrf = '{{ csrf_token }}';
|
||||
|
||||
const loadEmojis = async () => {
|
||||
try {
|
||||
const res = await fetch('/api/v2/emojis');
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
window.emojiAdmin.emojis = data.emojis;
|
||||
const grid = document.getElementById('emoji-list');
|
||||
if (!grid) return;
|
||||
grid.innerHTML = data.emojis.map(e =>
|
||||
'<div class="emoji-card">' +
|
||||
'<button class="emoji-delete" onclick="window.emojiAdmin.deleteEmoji(' + e.id + ')" title="Delete">✕</button>' +
|
||||
'<img class="emoji-preview" src="' + e.url + '" alt=":' + esc(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('');
|
||||
}
|
||||
} catch (err) { console.error('[EMOJI_ADMIN] Load Error:', err); }
|
||||
};
|
||||
function esc(s) {
|
||||
return (s || '').toString()
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
const addEmoji = async (e) => {
|
||||
/* 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 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';
|
||||
if (container.dataset.loaded) return;
|
||||
|
||||
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) {
|
||||
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>' +
|
||||
'</div>';
|
||||
}).join('') +
|
||||
'</div>';
|
||||
}
|
||||
container.dataset.loaded = '1';
|
||||
}
|
||||
|
||||
/* ── STANDALONE EMOJI ACTIONS ── */
|
||||
function addEmoji(e) {
|
||||
if (e) e.preventDefault();
|
||||
const name = document.getElementById('emoji-name').value;
|
||||
const fileInput = document.getElementById('emoji-file');
|
||||
|
||||
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');
|
||||
|
||||
const btn = document.getElementById('add-emoji');
|
||||
const oldText = btn.textContent;
|
||||
var btn = document.getElementById('add-emoji');
|
||||
var oldText = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = i18n.uploading || 'Uploading...';
|
||||
|
||||
const formData = new FormData();
|
||||
var formData = new FormData();
|
||||
formData.append('name', name);
|
||||
if (fileInput.files[0]) {
|
||||
formData.append('file', 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;
|
||||
var headers = { 'X-Requested-With': 'XMLHttpRequest' };
|
||||
if (csrf) headers['X-CSRF-Token'] = csrf;
|
||||
|
||||
const res = await fetch('/api/v2/admin/emojis', {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: formData
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
document.getElementById('emoji-name').value = '';
|
||||
document.getElementById('emoji-file').value = '';
|
||||
loadEmojis();
|
||||
} else {
|
||||
alert('Failed: ' + (data.message || data.msg || 'Unknown error'));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[EMOJI_ADMIN] Add Error:', e);
|
||||
alert('Error: ' + e.message);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = oldText;
|
||||
}
|
||||
};
|
||||
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; });
|
||||
}
|
||||
|
||||
const deleteEmoji = async (id) => {
|
||||
function deleteEmoji(id) {
|
||||
if (!confirm('Delete this emoji?')) return;
|
||||
try {
|
||||
const res = await fetch('/api/v2/admin/emojis/' + id + '/delete', {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-Token': '{{ csrf_token }}' }
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
loadEmojis();
|
||||
} else {
|
||||
alert('Delete failed');
|
||||
}
|
||||
} catch (e) { console.error(e); }
|
||||
};
|
||||
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); });
|
||||
}
|
||||
|
||||
const openEditModal = (id) => {
|
||||
const emoji = (window.emojiAdmin.emojis || []).find(e => e.id === id);
|
||||
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 = '';
|
||||
|
||||
const preview = document.getElementById('edit-emoji-preview');
|
||||
var preview = document.getElementById('edit-emoji-preview');
|
||||
preview.src = emoji.url;
|
||||
preview.alt = ':' + emoji.name + ':';
|
||||
|
||||
const modal = document.getElementById('edit-emoji-modal');
|
||||
var modal = document.getElementById('edit-emoji-modal');
|
||||
if (modal) modal.style.display = 'flex';
|
||||
};
|
||||
}
|
||||
|
||||
const closeEditModal = () => {
|
||||
const modal = document.getElementById('edit-emoji-modal');
|
||||
function closeEditModal() {
|
||||
var modal = document.getElementById('edit-emoji-modal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
document.getElementById('edit-emoji-file').value = '';
|
||||
};
|
||||
}
|
||||
|
||||
const saveEmoji = async () => {
|
||||
const id = document.getElementById('edit-emoji-id').value;
|
||||
const name = document.getElementById('edit-emoji-name').value.trim().toLowerCase();
|
||||
const fileInput = document.getElementById('edit-emoji-file');
|
||||
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.');
|
||||
|
||||
const btn = document.querySelector('#edit-emoji-modal .btn-save');
|
||||
const oldText = btn.textContent;
|
||||
var btn = document.querySelector('#edit-emoji-modal .btn-save');
|
||||
var oldText = btn.textContent;
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Saving...';
|
||||
|
||||
const formData = new FormData();
|
||||
var formData = new FormData();
|
||||
formData.append('name', name);
|
||||
if (fileInput.files[0]) {
|
||||
formData.append('file', fileInput.files[0]);
|
||||
}
|
||||
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;
|
||||
var headers = { 'X-Requested-With': 'XMLHttpRequest' };
|
||||
if (csrf) headers['X-CSRF-Token'] = csrf;
|
||||
|
||||
const res = await fetch('/api/v2/admin/emojis/' + id + '/edit', {
|
||||
method: 'POST',
|
||||
headers: headers,
|
||||
body: formData
|
||||
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';
|
||||
});
|
||||
}
|
||||
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
closeEditModal();
|
||||
loadEmojis();
|
||||
} else {
|
||||
alert('Save failed: ' + (data.message || data.msg || 'Unknown error'));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[EMOJI_ADMIN] Edit Error:', e);
|
||||
alert('Save failed: ' + e.message);
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
btn.textContent = oldText;
|
||||
}
|
||||
};
|
||||
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); });
|
||||
}
|
||||
|
||||
function renamePack(id) {
|
||||
var pack = (window.emojiAdmin.packs || []).find(function(p) { return p.id === id; });
|
||||
var currentName = pack ? pack.name : '';
|
||||
var newName = prompt('New pack name:', currentName);
|
||||
if (!newName || newName.trim() === currentName) return;
|
||||
fetch('/api/v2/admin/sticker-packs/' + id + '/rename', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf, 'X-Requested-With': 'XMLHttpRequest' },
|
||||
body: JSON.stringify({ name: newName.trim() })
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) { if (data.success) loadAll(); else alert('Rename failed: ' + (data.message || 'Unknown error')); })
|
||||
.catch(function(err) { alert('Error: ' + err.message); });
|
||||
}
|
||||
|
||||
// Global scope for onclick handlers
|
||||
window.emojiAdmin = { deleteEmoji, openEditModal, closeEditModal, saveEmoji, emojis: [] };
|
||||
window.emojiAdmin = {
|
||||
deleteEmoji: deleteEmoji,
|
||||
openEditModal: openEditModal,
|
||||
closeEditModal: closeEditModal,
|
||||
saveEmoji: saveEmoji,
|
||||
importPack: importPack,
|
||||
deletePack: deletePack,
|
||||
renamePack: renamePack,
|
||||
togglePackEmojis: togglePackEmojis,
|
||||
emojis: [],
|
||||
packs: []
|
||||
};
|
||||
|
||||
const btnAddEmoji = document.getElementById('add-emoji');
|
||||
if (btnAddEmoji) btnAddEmoji.addEventListener('click', addEmoji);
|
||||
document.getElementById('add-emoji').addEventListener('click', addEmoji);
|
||||
document.getElementById('sp-import-btn').addEventListener('click', importPack);
|
||||
|
||||
// Live Update Listener (SSE dispatched via f0ckm.js)
|
||||
document.addEventListener('f0ck:emojis_updated', loadEmojis);
|
||||
|
||||
loadEmojis();
|
||||
// Reload when SSE emits emojis_updated
|
||||
document.addEventListener('f0ck:emojis_updated', loadAll);
|
||||
|
||||
loadAll();
|
||||
})();
|
||||
</script>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user