renaming
This commit is contained in:
@@ -168,7 +168,7 @@ export default (router, tpl) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Rename a sticker pack (Admin)
|
// Rename a sticker pack (Admin) — optionally backfills emoji names and comment references
|
||||||
router.post(/\/api\/v2\/admin\/sticker-packs\/(?<id>\d+)\/rename/, async (req, res) => {
|
router.post(/\/api\/v2\/admin\/sticker-packs\/(?<id>\d+)\/rename/, async (req, res) => {
|
||||||
if (!req.session || !req.session.admin) {
|
if (!req.session || !req.session.admin) {
|
||||||
return res.reply({ code: 403, body: JSON.stringify({ success: false, message: "Forbidden" }) });
|
return res.reply({ code: 403, body: JSON.stringify({ success: false, message: "Forbidden" }) });
|
||||||
@@ -178,17 +178,85 @@ export default (router, tpl) => {
|
|||||||
return res.reply({ code: 403, body: JSON.stringify({ success: false, message: "Invalid CSRF token" }) });
|
return res.reply({ code: 403, body: JSON.stringify({ success: false, message: "Invalid CSRF token" }) });
|
||||||
}
|
}
|
||||||
const id = req.params.id;
|
const id = req.params.id;
|
||||||
const newName = (req.body?.name || req.post?.name || '').trim();
|
let body = {};
|
||||||
|
try {
|
||||||
|
const raw = req.body || req.post || {};
|
||||||
|
body = typeof raw === 'string' ? JSON.parse(raw) : raw;
|
||||||
|
} catch (_) {}
|
||||||
|
const newName = (body.name || '').trim();
|
||||||
|
const backfill = body.backfill === true;
|
||||||
if (!newName) {
|
if (!newName) {
|
||||||
return res.reply({ code: 400, body: JSON.stringify({ success: false, message: 'name is required' }) });
|
return res.reply({ code: 400, body: JSON.stringify({ success: false, message: 'name is required' }) });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (!backfill) {
|
||||||
|
// Simple rename only
|
||||||
await db`UPDATE sticker_packs SET name = ${newName} WHERE id = ${id}`;
|
await db`UPDATE sticker_packs SET name = ${newName} WHERE id = ${id}`;
|
||||||
await db`NOTIFY emojis_updated, '{}'`;
|
await db`NOTIFY emojis_updated, '{}'`;
|
||||||
return res.reply({ headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ success: true }) });
|
return res.reply({ headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ success: true }) });
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Backfill: rename emojis + update comment references ──────────
|
||||||
|
// Build new slug from new display name (same logic as import)
|
||||||
|
const newSlug = newName
|
||||||
|
.replace(/[^a-z0-9]/gi, '_')
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/_+/g, '_')
|
||||||
|
.replace(/^_|_$/, '');
|
||||||
|
|
||||||
|
// Load all emojis in this pack
|
||||||
|
const emojis = await db`SELECT id, name FROM custom_emojis WHERE pack_id = ${id} ORDER BY id`;
|
||||||
|
|
||||||
|
// Build rename map: old_name → new_name
|
||||||
|
const renames = [];
|
||||||
|
for (const emoji of emojis) {
|
||||||
|
const suffixMatch = emoji.name.match(/_(\d+)$/);
|
||||||
|
if (!suffixMatch) continue; // skip non-standard names
|
||||||
|
const newEmojiName = newSlug + '_' + suffixMatch[1].padStart(3, '0');
|
||||||
|
if (newEmojiName !== emoji.name) {
|
||||||
|
renames.push({ id: emoji.id, oldName: emoji.name, newName: newEmojiName });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply everything in a transaction
|
||||||
|
let emojiRenamed = 0;
|
||||||
|
let commentRowsUpdated = 0;
|
||||||
|
|
||||||
|
await db.begin(async sql => {
|
||||||
|
// Rename the pack
|
||||||
|
await sql`UPDATE sticker_packs SET name = ${newName} WHERE id = ${id}`;
|
||||||
|
|
||||||
|
for (const r of renames) {
|
||||||
|
// Check name conflict (another emoji already has this name)
|
||||||
|
const [conflict] = await sql`SELECT id FROM custom_emojis WHERE name = ${r.newName} AND id != ${r.id} LIMIT 1`;
|
||||||
|
if (conflict) {
|
||||||
|
console.warn(`[RENAME] Skipping ${r.oldName} → ${r.newName}: name conflict`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Rename the emoji
|
||||||
|
await sql`UPDATE custom_emojis SET name = ${r.newName} WHERE id = ${r.id}`;
|
||||||
|
emojiRenamed++;
|
||||||
|
|
||||||
|
// Replace :old_name: with :new_name: in all comments
|
||||||
|
const result = await sql`
|
||||||
|
UPDATE comments
|
||||||
|
SET content = REPLACE(content, ${':' + r.oldName + ':'}, ${':' + r.newName + ':'})
|
||||||
|
WHERE content LIKE ${'%:' + r.oldName + ':%'}
|
||||||
|
`;
|
||||||
|
commentRowsUpdated += result.count ?? 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await db`NOTIFY emojis_updated, '{}'`;
|
||||||
|
return res.reply({
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ success: true, emojiRenamed, commentRowsUpdated })
|
||||||
|
});
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error('[RENAME PACK]', e);
|
||||||
return res.reply({ code: 500, body: JSON.stringify({ success: false, message: "Database error" }) });
|
return res.reply({ code: 500, body: JSON.stringify({ success: false, message: "Database error: " + e.message }) });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -199,6 +199,14 @@ export const handleImportTelegramPack = async (req, res) => {
|
|||||||
let failed = 0;
|
let failed = 0;
|
||||||
const errors = [];
|
const errors = [];
|
||||||
|
|
||||||
|
// Derive a slug from the pack's display name (admin-supplied > Telegram title > URL slug)
|
||||||
|
// e.g. "My Cool Pack" → "my_cool_pack"
|
||||||
|
const safeName = packTitle
|
||||||
|
.replace(/[^a-z0-9]/gi, '_')
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/_+/g, '_')
|
||||||
|
.replace(/^_|_$/g, '') || tgName.replace(/[^a-z0-9_]/gi, '_').toLowerCase();
|
||||||
|
|
||||||
for (let i = 0; i < stickers.length; i++) {
|
for (let i = 0; i < stickers.length; i++) {
|
||||||
const sticker = stickers[i];
|
const sticker = stickers[i];
|
||||||
try {
|
try {
|
||||||
@@ -226,8 +234,7 @@ export const handleImportTelegramPack = async (req, res) => {
|
|||||||
const downloadUrl = 'https://api.telegram.org/file/bot' + botToken + '/' + filePath;
|
const downloadUrl = 'https://api.telegram.org/file/bot' + botToken + '/' + filePath;
|
||||||
const buffer = await downloadBuffer(downloadUrl);
|
const buffer = await downloadBuffer(downloadUrl);
|
||||||
|
|
||||||
// Generate emoji name from pack name + index
|
// Generate emoji name: <pack_display_slug>_<padded_index>
|
||||||
const safeName = tgName.replace(/[^a-z0-9_]/gi, '_').toLowerCase();
|
|
||||||
const emojiName = safeName + '_' + String(i + 1).padStart(3, '0');
|
const emojiName = safeName + '_' + String(i + 1).padStart(3, '0');
|
||||||
|
|
||||||
// Check for name conflict
|
// Check for name conflict
|
||||||
|
|||||||
@@ -185,6 +185,8 @@
|
|||||||
function renderPackGrid(packId) {
|
function renderPackGrid(packId) {
|
||||||
var container = document.getElementById('sp-emojis-' + packId);
|
var container = document.getElementById('sp-emojis-' + packId);
|
||||||
if (!container) return;
|
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 pack = (window.emojiAdmin.packs || []).find(function(p) { return p.id === packId; });
|
||||||
var currentThumb = pack ? (pack.thumb_url || '') : '';
|
var currentThumb = pack ? (pack.thumb_url || '') : '';
|
||||||
var packEmojis = (window.emojiAdmin.emojis || []).filter(function(e) { return e.pack_id === packId; });
|
var packEmojis = (window.emojiAdmin.emojis || []).filter(function(e) { return e.pack_id === packId; });
|
||||||
@@ -197,7 +199,7 @@
|
|||||||
var btnBg = isCurrent ? 'rgba(40,167,69,0.18)' : 'rgba(255,200,0,0.15)';
|
var btnBg = isCurrent ? 'rgba(40,167,69,0.18)' : 'rgba(255,200,0,0.15)';
|
||||||
var btnClr = isCurrent ? '#4caf50' : '#ffd700';
|
var btnClr = isCurrent ? '#4caf50' : '#ffd700';
|
||||||
var btnBdr = isCurrent ? 'rgba(40,167,69,0.35)' : 'rgba(255,200,0,0.3)';
|
var btnBdr = isCurrent ? 'rgba(40,167,69,0.35)' : 'rgba(255,200,0,0.3)';
|
||||||
var btnLbl = isCurrent ? '✓ Current Icon' : '⭐ Set as icon';
|
var btnLbl = isCurrent ? '\u2713 Current Icon' : '\u2B50 Set as icon';
|
||||||
return '<div class="emoji-card" style="position:relative;">' +
|
return '<div class="emoji-card" style="position:relative;">' +
|
||||||
'<button class="emoji-delete" onclick="window.emojiAdmin.deleteEmoji(' + e.id + ')" title="Delete">x</button>' +
|
'<button class="emoji-delete" onclick="window.emojiAdmin.deleteEmoji(' + e.id + ')" title="Delete">x</button>' +
|
||||||
stickerEl(e.url, e.name, ' loading="lazy"') +
|
stickerEl(e.url, e.name, ' loading="lazy"') +
|
||||||
@@ -207,6 +209,9 @@
|
|||||||
'</div>';
|
'</div>';
|
||||||
}).join('') +
|
}).join('') +
|
||||||
'</div>';
|
'</div>';
|
||||||
|
// Restore scroll position
|
||||||
|
var newGrid = container.querySelector('.emoji-grid');
|
||||||
|
if (newGrid && prevScroll) newGrid.scrollTop = prevScroll;
|
||||||
}
|
}
|
||||||
container.dataset.loaded = '1';
|
container.dataset.loaded = '1';
|
||||||
}
|
}
|
||||||
@@ -380,13 +385,26 @@
|
|||||||
var currentName = pack ? pack.name : '';
|
var currentName = pack ? pack.name : '';
|
||||||
var newName = prompt('New pack name:', currentName);
|
var newName = prompt('New pack name:', currentName);
|
||||||
if (!newName || newName.trim() === currentName) return;
|
if (!newName || newName.trim() === currentName) return;
|
||||||
|
newName = newName.trim();
|
||||||
|
|
||||||
|
var backfill = confirm('Rename all stickers too? OK = rename sticker names (old_001 -> new_001) and update comments. Cancel = pack label only.');
|
||||||
|
|
||||||
fetch('/api/v2/admin/sticker-packs/' + id + '/rename', {
|
fetch('/api/v2/admin/sticker-packs/' + id + '/rename', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf, 'X-Requested-With': 'XMLHttpRequest' },
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf, 'X-Requested-With': 'XMLHttpRequest' },
|
||||||
body: JSON.stringify({ name: newName.trim() })
|
body: JSON.stringify({ name: newName, backfill: backfill })
|
||||||
})
|
})
|
||||||
.then(function(r) { return r.json(); })
|
.then(function(r) { return r.json(); })
|
||||||
.then(function(data) { if (data.success) loadAll(); else alert('Rename failed: ' + (data.message || 'Unknown error')); })
|
.then(function(data) {
|
||||||
|
if (data.success) {
|
||||||
|
if (backfill) {
|
||||||
|
alert('Done! ' + (data.emojiRenamed || 0) + ' sticker(s) renamed, ' + (data.commentRowsUpdated || 0) + ' comment row(s) updated.');
|
||||||
|
}
|
||||||
|
loadAll();
|
||||||
|
} else {
|
||||||
|
alert('Rename failed: ' + (data.message || 'Unknown error'));
|
||||||
|
}
|
||||||
|
})
|
||||||
.catch(function(err) { alert('Error: ' + err.message); });
|
.catch(function(err) { alert('Error: ' + err.message); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user