feat(branding): system name/logo, horizontal logo on all pages
This commit is contained in:
+5
-1
@@ -13,4 +13,8 @@ if (details) {
|
||||
}
|
||||
})();
|
||||
|
||||
document.querySelector('.error-actions button').addEventListener('click', () => history.back());
|
||||
document.querySelector('.error-actions button').addEventListener('click', () => history.back());
|
||||
|
||||
fetch('/api/public-settings').then(r => r.ok ? r.json() : null).then(s => {
|
||||
if (s && (s.system_name || '').trim()) document.title = `Ошибка — ${s.system_name}`;
|
||||
}).catch(() => {});
|
||||
@@ -202,6 +202,17 @@ async function resolveModuleId(name) {
|
||||
try {
|
||||
const fRes = await fetch(`${API}/api/public-settings`);
|
||||
const f = await fRes.json();
|
||||
const title = document.getElementById('title');
|
||||
if (title && (f.system_name || '').trim()) {
|
||||
title.textContent = f.system_name;
|
||||
document.title = f.system_name;
|
||||
}
|
||||
const brandLogo = document.getElementById('brandLogo');
|
||||
if (brandLogo && f.system_logo) {
|
||||
brandLogo.src = f.system_logo;
|
||||
brandLogo.hidden = false;
|
||||
brandLogo.alt = f.system_name || '';
|
||||
}
|
||||
if (f.footer_left) document.getElementById('footerLeft').textContent = f.footer_left;
|
||||
if (f.footer_right) document.getElementById('footerRight').textContent = f.footer_right;
|
||||
const c = parseInt(f.spam_interval_min, 10);
|
||||
|
||||
+20
-1
@@ -28,4 +28,23 @@ async function doLogin() {
|
||||
|
||||
document.getElementById('pwdInput').addEventListener('keydown', e => { if (e.key === 'Enter') doLogin(); });
|
||||
document.getElementById('userInput').addEventListener('keydown', e => { if (e.key === 'Enter') doLogin(); });
|
||||
document.querySelector('.login button').addEventListener('click', doLogin);
|
||||
document.querySelector('.login button').addEventListener('click', doLogin);
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const res = await fetch(`${API}/api/public-settings`);
|
||||
if (!res.ok) return;
|
||||
const s = await res.json();
|
||||
const title = document.getElementById('loginTitle');
|
||||
if (title && (s.system_name || '').trim()) title.textContent = s.system_name;
|
||||
const logo = document.getElementById('loginLogo');
|
||||
if (logo && s.system_logo) {
|
||||
const img = document.createElement('img');
|
||||
img.src = s.system_logo;
|
||||
img.alt = '';
|
||||
logo.appendChild(img);
|
||||
logo.style.display = 'flex';
|
||||
}
|
||||
document.title = title ? title.textContent + ' — Вход' : document.title;
|
||||
} catch { /* ignore */ }
|
||||
})();
|
||||
+11
-3
@@ -1,4 +1,12 @@
|
||||
const state={token:'',password:null,showNames:true,cdTarget:0,cdTimer:null,cdBound:false};
|
||||
const state={token:'',password:null,showNames:true,cdTarget:0,cdTimer:null,cdBound:false,systemName:'WhatIDo'};
|
||||
|
||||
function loadBrand(){
|
||||
return fetch('/api/public-settings').then(function(res){
|
||||
return res.ok?res.json():null;
|
||||
}).then(function(s){
|
||||
if(s&&(s.system_name||'').trim())state.systemName=s.system_name;
|
||||
}).catch(function(){});
|
||||
}
|
||||
|
||||
function esc(s){return String(s==null?'':s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"').replace(/'/g,''')}
|
||||
function pad(n){return String(n).padStart(2,'0')}
|
||||
@@ -440,7 +448,7 @@ function renderFooter(data,heading,pool){
|
||||
const label=btn.querySelector('.text-block-wrap-div');
|
||||
if(label)label.textContent='Перейти по ссылке';
|
||||
}
|
||||
setText('footCopy','WhatIDo © '+new Date().getFullYear());
|
||||
setText('footCopy',state.systemName+' © '+new Date().getFullYear());
|
||||
setText('footBrand',heading);
|
||||
const fbox=document.getElementById('footerPhotoBox');
|
||||
const fimg=document.getElementById('footerPhoto');
|
||||
@@ -542,5 +550,5 @@ function bindEvents(){
|
||||
|
||||
bindEvents();
|
||||
makeNavLinks();
|
||||
loadReport();
|
||||
loadBrand().then(function(){loadReport()});
|
||||
function closeLightbox(){const lb=document.getElementById('lightbox');if(lb)lb.classList.remove('is-open')}
|
||||
+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