feat(branding): system name/logo, horizontal logo on all pages
This commit is contained in:
+83
-2
@@ -1,5 +1,5 @@
|
||||
const DIRTY_FIELDS = ['spamInterval', 'footerLeft', 'footerRight', 'aiPrompt', 'aiAutoCheck', 'shareShowMessage', 'shareShowDate', 'shareShowNames', 'shareShowPhotos', 'cookieNoticeText', 'photoResolution', 'photoQuality', 'photoEngine'];
|
||||
const SECTION_IDS = ['sec-system', 'sec-antispam', 'sec-share', 'sec-form', 'sec-ai', 'sec-backup', 'sec-bans'];
|
||||
const DIRTY_FIELDS = ['systemName', 'spamInterval', 'footerLeft', 'footerRight', 'aiPrompt', 'aiAutoCheck', 'shareShowMessage', 'shareShowDate', 'shareShowNames', 'shareShowPhotos', 'cookieNoticeText', 'photoResolution', 'photoQuality', 'photoEngine'];
|
||||
const SECTION_IDS = ['sec-system', 'sec-brand', 'sec-antispam', 'sec-share', 'sec-ai', 'sec-backup', 'sec-form', 'sec-bans'];
|
||||
const SPAM_MAX = 10080;
|
||||
const BAN_REASONS = {
|
||||
'honeypot': 'Антиспам-поле',
|
||||
@@ -8,6 +8,21 @@ const BAN_REASONS = {
|
||||
'manual': 'Вручную',
|
||||
};
|
||||
|
||||
let currentLogo = '';
|
||||
let logoOriginal = '';
|
||||
|
||||
function renderLogoPreview() {
|
||||
const img = document.getElementById('logoPreviewImg');
|
||||
const fb = document.getElementById('logoPreviewFallback');
|
||||
const rm = document.getElementById('logoRemoveBtn');
|
||||
if (!img || !fb || !rm) return;
|
||||
const hasLogo = !!currentLogo;
|
||||
img.style.display = hasLogo ? 'block' : 'none';
|
||||
fb.style.display = hasLogo ? 'none' : '';
|
||||
if (hasLogo) img.src = currentLogo;
|
||||
rm.hidden = !hasLogo;
|
||||
}
|
||||
|
||||
function fieldValue(el) {
|
||||
return el.type === 'checkbox' ? String(el.checked) : el.value;
|
||||
}
|
||||
@@ -101,6 +116,8 @@ function revertChanges() {
|
||||
if (el.type === 'checkbox') el.checked = original === 'true';
|
||||
else el.value = original;
|
||||
});
|
||||
currentLogo = logoOriginal;
|
||||
renderLogoPreview();
|
||||
clearFieldError('spamInterval');
|
||||
updateSpamUi();
|
||||
updateDirty();
|
||||
@@ -143,6 +160,10 @@ async function loadSettings() {
|
||||
]);
|
||||
if (!sRes.ok) { showError('Не удалось загрузить настройки'); return; }
|
||||
const s = await sRes.json();
|
||||
document.getElementById('systemName').value = s.system_name !== undefined ? s.system_name : 'WhatIDo';
|
||||
currentLogo = s.system_logo || '';
|
||||
logoOriginal = currentLogo;
|
||||
renderLogoPreview();
|
||||
if (s.spam_interval_min !== undefined) document.getElementById('spamInterval').value = s.spam_interval_min;
|
||||
document.getElementById('footerLeft').value = s.footer_left || '';
|
||||
document.getElementById('footerRight').value = s.footer_right || '';
|
||||
@@ -487,6 +508,7 @@ async function saveSettings() {
|
||||
method: 'PUT',
|
||||
headers: hdrJson(),
|
||||
body: JSON.stringify({ settings: {
|
||||
system_name: document.getElementById('systemName').value.trim(),
|
||||
spam_interval_min: String(Math.round(Number(v))),
|
||||
footer_left: document.getElementById('footerLeft').value.trim(),
|
||||
footer_right: document.getElementById('footerRight').value.trim(),
|
||||
@@ -504,6 +526,7 @@ async function saveSettings() {
|
||||
});
|
||||
if (res.ok) {
|
||||
showToast('Настройки сохранены');
|
||||
logoOriginal = currentLogo;
|
||||
snapshotDirty();
|
||||
updateSpamUi();
|
||||
} else {
|
||||
@@ -637,6 +660,64 @@ if (statsRefreshBtn) statsRefreshBtn.addEventListener('click', async () => {
|
||||
statsRefreshBtn.classList.remove('spinning');
|
||||
});
|
||||
|
||||
function logoStatusText(html) {
|
||||
const st = document.getElementById('logoUploadStatus');
|
||||
if (st) st.innerHTML = html;
|
||||
}
|
||||
|
||||
document.getElementById('logoUploadBtn').addEventListener('click', () => document.getElementById('logoInput').click());
|
||||
|
||||
document.getElementById('logoInput').addEventListener('change', async (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
const btn = document.getElementById('logoUploadBtn');
|
||||
if (file.size > 1024 * 1024) {
|
||||
e.target.value = '';
|
||||
showError('Логотип не должен превышать 1 МБ');
|
||||
return;
|
||||
}
|
||||
btn.disabled = true;
|
||||
logoStatusText('<span class="spinner dark"></span> Загрузка...');
|
||||
try {
|
||||
const fd = new FormData();
|
||||
fd.append('logo', file);
|
||||
const res = await fetch(`${API}/api/settings/logo`, { method: 'POST', headers: hdr(), body: fd });
|
||||
const d = await res.json().catch(() => ({}));
|
||||
if (res.ok) {
|
||||
currentLogo = d.system_logo || '';
|
||||
renderLogoPreview();
|
||||
logoStatusText('');
|
||||
showToast('Логотип обновлён');
|
||||
} else {
|
||||
logoStatusText(`<span class="fail">${esc(d.error || 'Ошибка загрузки')}</span>`);
|
||||
}
|
||||
} catch {
|
||||
logoStatusText('<span class="fail">Ошибка сети</span>');
|
||||
} finally {
|
||||
btn.disabled = false;
|
||||
e.target.value = '';
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('logoRemoveBtn').addEventListener('click', async () => {
|
||||
if (!confirm('Удалить логотип? Название системы сохранится.')) return;
|
||||
logoStatusText('<span class="spinner dark"></span> Удаление...');
|
||||
try {
|
||||
const res = await fetch(`${API}/api/settings/logo`, { method: 'DELETE', headers: hdr() });
|
||||
if (res.ok) {
|
||||
currentLogo = '';
|
||||
logoOriginal = '';
|
||||
renderLogoPreview();
|
||||
logoStatusText('');
|
||||
showToast('Логотип удалён');
|
||||
} else {
|
||||
logoStatusText('<span class="fail">Ошибка удаления</span>');
|
||||
}
|
||||
} catch {
|
||||
logoStatusText('<span class="fail">Ошибка сети</span>');
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', e => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 's') {
|
||||
e.preventDefault();
|
||||
|
||||
Reference in New Issue
Block a user