mirror of
https://github.com/sartoopjj/thefeed.git
synced 2026-05-19 10:46:51 +03:00
feat: add background image handling and language/theme settings API
This commit is contained in:
@@ -2060,7 +2060,12 @@
|
||||
// Re-render dynamic content
|
||||
if (channels.length > 0) renderChannels();
|
||||
}
|
||||
function setLang(l) { lang = l; localStorage.setItem('thefeed_lang', l); applyLang() }
|
||||
function setLang(l) {
|
||||
lang = l;
|
||||
localStorage.setItem('thefeed_lang', l);
|
||||
applyLang();
|
||||
fetch('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ lang: l }) }).catch(function () { });
|
||||
}
|
||||
|
||||
// ===== STATE =====
|
||||
var selectedChannel = 0, channels = [], eventSource = null, autoRefreshTimer = null, telegramLoggedIn = false, logVisible = false;
|
||||
@@ -2124,6 +2129,16 @@
|
||||
document.getElementById('fontSizeVal').textContent = s.fontSize;
|
||||
}
|
||||
if (s.debug) document.getElementById('cfgDebug').checked = true;
|
||||
if (s.theme && (s.theme === 'dark' || s.theme === 'light')) {
|
||||
localStorage.setItem('thefeed_theme', s.theme);
|
||||
document.documentElement.setAttribute('data-theme', s.theme);
|
||||
applyThemeButtons();
|
||||
}
|
||||
if (s.lang && (s.lang === 'fa' || s.lang === 'en')) {
|
||||
lang = s.lang;
|
||||
localStorage.setItem('thefeed_lang', s.lang);
|
||||
applyLang();
|
||||
}
|
||||
if (s.version) { appVersion = s.version; renderAppVersion(s.version, s.commit); }
|
||||
renderLatestVersion();
|
||||
} catch (e) { }
|
||||
@@ -2181,6 +2196,7 @@
|
||||
localStorage.setItem('thefeed_theme', t);
|
||||
document.documentElement.setAttribute('data-theme', t);
|
||||
applyThemeButtons();
|
||||
fetch('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ theme: t }) }).catch(function () { });
|
||||
}
|
||||
function applyThemeButtons() {
|
||||
var cur = localStorage.getItem('thefeed_theme') || 'dark';
|
||||
@@ -3620,34 +3636,36 @@
|
||||
}
|
||||
|
||||
// ===== BACKGROUND IMAGE =====
|
||||
function _setBg(data) {
|
||||
function _setBg(url) {
|
||||
var ca = document.querySelector('.chat-area');
|
||||
ca.style.backgroundImage = data ? 'url("' + data + '")' : '';
|
||||
ca.style.backgroundSize = data ? 'cover' : '';
|
||||
ca.style.backgroundPosition = data ? 'center' : '';
|
||||
ca.style.backgroundRepeat = data ? 'no-repeat' : '';
|
||||
document.getElementById('messages').style.background = data ? 'transparent' : '';
|
||||
ca.style.backgroundImage = url ? 'url("' + url + '")' : '';
|
||||
ca.style.backgroundSize = url ? 'cover' : '';
|
||||
ca.style.backgroundPosition = url ? 'center' : '';
|
||||
ca.style.backgroundRepeat = url ? 'no-repeat' : '';
|
||||
document.getElementById('messages').style.background = url ? 'transparent' : '';
|
||||
}
|
||||
function loadBgImage() {
|
||||
var data = localStorage.getItem('thefeed_bg_image') || '';
|
||||
if (data) _setBg(data);
|
||||
// Use cache-busting query to ensure latest image.
|
||||
var url = '/api/bg-image?t=' + Date.now();
|
||||
fetch(url).then(function (r) {
|
||||
if (r.status === 204 || !r.ok) return;
|
||||
_setBg('/api/bg-image?t=' + Date.now());
|
||||
}).catch(function () { });
|
||||
}
|
||||
function applyBgImage() {
|
||||
async function applyBgImage() {
|
||||
var inp = document.getElementById('bgImageInput');
|
||||
if (!inp.files || !inp.files[0]) return;
|
||||
var file = inp.files[0];
|
||||
if (file.size > 5 * 1024 * 1024) { showToast('File too large (max 5MB)'); return }
|
||||
var reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
var data = e.target.result;
|
||||
try { localStorage.setItem('thefeed_bg_image', data) } catch (ex) { showToast('File too large for storage'); return }
|
||||
_setBg(data);
|
||||
if (file.size > 10 * 1024 * 1024) { showToast('File too large (max 10MB)'); return }
|
||||
try {
|
||||
var r = await fetch('/api/bg-image', { method: 'POST', body: file });
|
||||
if (!r.ok) { showToast(await r.text()); return }
|
||||
_setBg('/api/bg-image?t=' + Date.now());
|
||||
showToast(t('apply'));
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
} catch (e) { showToast(e.message); }
|
||||
}
|
||||
function clearBgImage() {
|
||||
localStorage.removeItem('thefeed_bg_image');
|
||||
async function clearBgImage() {
|
||||
try { await fetch('/api/bg-image', { method: 'DELETE' }) } catch (e) { }
|
||||
_setBg('');
|
||||
document.getElementById('bgImageInput').value = '';
|
||||
showToast(t('clear_bg'));
|
||||
|
||||
+61
-5
@@ -8,6 +8,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
mrand "math/rand/v2"
|
||||
@@ -54,8 +55,10 @@ type ProfileList struct {
|
||||
Active string `json:"active"` // ID of active profile
|
||||
Profiles []Profile `json:"profiles"`
|
||||
// FontSize stores user's preferred font size (0 = default 14).
|
||||
FontSize int `json:"fontSize,omitempty"`
|
||||
Debug bool `json:"debug,omitempty"`
|
||||
FontSize int `json:"fontSize,omitempty"`
|
||||
Debug bool `json:"debug,omitempty"`
|
||||
Theme string `json:"theme,omitempty"`
|
||||
Lang string `json:"lang,omitempty"`
|
||||
}
|
||||
|
||||
// lastScanData is the on-disk structure for last_scan.json.
|
||||
@@ -186,6 +189,7 @@ func (s *Server) Run() error {
|
||||
mux.HandleFunc("/api/settings", s.handleSettings)
|
||||
mux.HandleFunc("/api/version-check", s.handleVersionCheck)
|
||||
mux.HandleFunc("/api/cache/clear", s.handleClearCache)
|
||||
mux.HandleFunc("/api/bg-image", s.handleBgImage)
|
||||
mux.HandleFunc("/api/resolvers/apply-saved", s.handleApplySavedResolvers)
|
||||
mux.HandleFunc("/api/resolvers/active", s.handleActiveResolvers)
|
||||
mux.HandleFunc("/api/resolvers/remove", s.handleRemoveResolver)
|
||||
@@ -1482,12 +1486,14 @@ func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request) {
|
||||
if pl == nil {
|
||||
pl = &ProfileList{}
|
||||
}
|
||||
writeJSON(w, map[string]any{"fontSize": pl.FontSize, "debug": pl.Debug, "version": version.Version, "commit": version.Commit})
|
||||
writeJSON(w, map[string]any{"fontSize": pl.FontSize, "debug": pl.Debug, "theme": pl.Theme, "lang": pl.Lang, "version": version.Version, "commit": version.Commit})
|
||||
|
||||
case http.MethodPost:
|
||||
var req struct {
|
||||
FontSize int `json:"fontSize"`
|
||||
Debug bool `json:"debug"`
|
||||
FontSize int `json:"fontSize"`
|
||||
Debug bool `json:"debug"`
|
||||
Theme string `json:"theme"`
|
||||
Lang string `json:"lang"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
http.Error(w, "invalid JSON", 400)
|
||||
@@ -1505,6 +1511,12 @@ func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
pl.FontSize = req.FontSize
|
||||
pl.Debug = req.Debug
|
||||
if req.Theme == "dark" || req.Theme == "light" {
|
||||
pl.Theme = req.Theme
|
||||
}
|
||||
if req.Lang == "fa" || req.Lang == "en" {
|
||||
pl.Lang = req.Lang
|
||||
}
|
||||
if err := s.saveProfiles(pl); err != nil {
|
||||
http.Error(w, fmt.Sprintf("save: %v", err), 500)
|
||||
return
|
||||
@@ -1524,6 +1536,50 @@ func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleBgImage(w http.ResponseWriter, r *http.Request) {
|
||||
bgPath := filepath.Join(s.dataDir, "bg_image")
|
||||
switch r.Method {
|
||||
case http.MethodGet:
|
||||
data, err := os.ReadFile(bgPath)
|
||||
if err != nil {
|
||||
w.Header().Set("Content-Type", "application/octet-stream")
|
||||
w.WriteHeader(204)
|
||||
return
|
||||
}
|
||||
// Detect content type from file data.
|
||||
ct := http.DetectContentType(data)
|
||||
w.Header().Set("Content-Type", ct)
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Write(data)
|
||||
|
||||
case http.MethodPost:
|
||||
// Limit upload to 10 MB.
|
||||
r.Body = http.MaxBytesReader(w, r.Body, 10<<20)
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "file too large (max 10MB)", 413)
|
||||
return
|
||||
}
|
||||
ct := http.DetectContentType(data)
|
||||
if !strings.HasPrefix(ct, "image/") {
|
||||
http.Error(w, "not an image", 400)
|
||||
return
|
||||
}
|
||||
if err := os.WriteFile(bgPath, data, 0600); err != nil {
|
||||
http.Error(w, fmt.Sprintf("save: %v", err), 500)
|
||||
return
|
||||
}
|
||||
writeJSON(w, map[string]any{"ok": true})
|
||||
|
||||
case http.MethodDelete:
|
||||
os.Remove(bgPath)
|
||||
writeJSON(w, map[string]any{"ok": true})
|
||||
|
||||
default:
|
||||
http.Error(w, "method not allowed", 405)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleVersionCheck(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != http.MethodPost {
|
||||
http.Error(w, "method not allowed", 405)
|
||||
|
||||
Reference in New Issue
Block a user