feat(themes): custom CSS themes per user (upload, paste, live)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m21s
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m21s
- Theme manager in settings: upload a .css file or paste CSS, template
download/copy/insert, edit and delete; custom themes show up in the
gallery and the quick switchers
- Themes are injected live as <style id="mindsift-custom-theme">; the
active one is cached so it applies on boot without flashing
- Backend: user_themes table and GET/POST /themes, DELETE /themes/{id}
(per user, max 200 KB)
- Docs: theming, REST, database
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
84395f3322
commit
bf0d68e7e1
15 files changed
+653
-51
No files matched your search
@@ -110,6 +110,9 @@ func (s *Server) Router() http.Handler {
|
||||
r.Get("/api/v1/stats", s.handleStats)
|
||||
r.Get("/api/v1/search", s.handleSearch)
|
||||
r.Get("/api/v1/export", s.handleExport)
|
||||
r.Get("/api/v1/themes", s.handleGetThemes)
|
||||
r.Post("/api/v1/themes", s.handleSaveTheme)
|
||||
r.Delete("/api/v1/themes/{id}", s.handleDeleteTheme)
|
||||
|
||||
// Instant Capture & Thoughts
|
||||
r.Post("/api/v1/inbox", s.handleCaptureInbox)
|
||||
@@ -2049,6 +2052,49 @@ func (s *Server) handleSearch(w http.ResponseWriter, r *http.Request) {
|
||||
})
|
||||
}
|
||||
|
||||
// --- CUSTOM THEMES ---
|
||||
|
||||
func (s *Server) handleGetThemes(w http.ResponseWriter, r *http.Request) {
|
||||
themes, err := s.store.GetUserThemes(auth.GetUserID(r.Context()))
|
||||
if err != nil {
|
||||
writeError(w, http.StatusInternalServerError, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, themes)
|
||||
}
|
||||
|
||||
// handleSaveTheme creates a theme, or updates it when an id of the caller's theme is given.
|
||||
func (s *Server) handleSaveTheme(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CSS string `json:"css"`
|
||||
}
|
||||
r.Body = http.MaxBytesReader(w, r.Body, storage.MaxThemeCSSBytes+16*1024)
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
writeError(w, http.StatusBadRequest, "Ungültige Anfrage oder Theme zu groß")
|
||||
return
|
||||
}
|
||||
theme, err := s.store.SaveUserTheme(auth.GetUserID(r.Context()), strings.TrimSpace(req.ID), req.Name, req.CSS)
|
||||
if err != nil {
|
||||
writeError(w, http.StatusBadRequest, err.Error())
|
||||
return
|
||||
}
|
||||
status := http.StatusCreated
|
||||
if req.ID != "" {
|
||||
status = http.StatusOK
|
||||
}
|
||||
writeJSON(w, status, theme)
|
||||
}
|
||||
|
||||
func (s *Server) handleDeleteTheme(w http.ResponseWriter, r *http.Request) {
|
||||
if err := s.store.DeleteUserTheme(auth.GetUserID(r.Context()), chi.URLParam(r, "id")); err != nil {
|
||||
writeError(w, http.StatusNotFound, err.Error())
|
||||
return
|
||||
}
|
||||
writeJSON(w, http.StatusOK, map[string]bool{"deleted": true})
|
||||
}
|
||||
|
||||
func (s *Server) handleExport(w http.ResponseWriter, r *http.Request) {
|
||||
userID := auth.GetUserID(r.Context())
|
||||
exportData, err := s.store.ExportUserData(userID)
|
||||
|
||||
Reference in new issue
Block a user