diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c91f5bd --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,12 @@ +# Changelog + +All notable changes to this project are documented in this file. + +## [1.113.0] - 2026-07-08 + +### Added +- Styling settings: "CSS Variables" panel with a color picker and description for every + customizable color token in `globals.css` (base colors, settings sidebar, design token + aliases, event colors, all-day chips). +- Styling settings: "Custom CSS" textarea to inject a personal stylesheet that loads after + the app's default styles and can override any rule. diff --git a/package.json b/package.json index 342af3f..461dded 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.112.2", + "version": "1.113.0", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "main": "index.js", "scripts": { diff --git a/prisma/migrations/20260708_add_custom_css/migration.sql b/prisma/migrations/20260708_add_custom_css/migration.sql new file mode 100644 index 0000000..a6a30f0 --- /dev/null +++ b/prisma/migrations/20260708_add_custom_css/migration.sql @@ -0,0 +1,3 @@ +-- User: per-user CSS variable overrides and free-form custom stylesheet (Styling settings tab) +ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "customCssVars" JSONB; +ALTER TABLE "User" ADD COLUMN IF NOT EXISTS "customCss" TEXT; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 25fbfa6..26def83 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -116,6 +116,8 @@ model User { weatherLon Float? viewSettings Json? weatherRecentCities Json? + customCssVars Json? + customCss String? @db.Text hasCompletedOnboarding Boolean @default(true) showCalendarProviderIcon Boolean @default(false) headerCurrentDayFormat String? @default("DDD, DD. MMMM YYYY") diff --git a/src/app/api/user/profile/route.ts b/src/app/api/user/profile/route.ts index d6d6e79..6afc883 100644 --- a/src/app/api/user/profile/route.ts +++ b/src/app/api/user/profile/route.ts @@ -111,6 +111,8 @@ export async function GET(request: NextRequest) { weatherLocation: true, weatherRecentCities: true, viewSettings: true, + customCssVars: true, + customCss: true, hasCompletedOnboarding: true, createdAt: true } @@ -156,6 +158,7 @@ export async function PATCH(request: NextRequest) { showCompletedTasks, showLines, startDayOffset, weekdayFormat, weekdayCase, customWeekdayNames, weekStartDay, quoteSourceUrls, quoteLanguages, kanbanStages, lightTheme, darkTheme, notificationsEnabled, mobileFontScale, weatherEnabled, weatherLat, weatherLon, weatherLocation, weatherRecentCities, viewSettings, + customCssVars, customCss, hasCompletedOnboarding } = body; @@ -256,6 +259,8 @@ export async function PATCH(request: NextRequest) { ...(weatherLocation !== undefined && { weatherLocation }), ...(weatherRecentCities !== undefined && { weatherRecentCities }), ...(viewSettings !== undefined && { viewSettings }), + ...(customCssVars !== undefined && { customCssVars }), + ...(customCss !== undefined && { customCss }), ...(hasCompletedOnboarding !== undefined && { hasCompletedOnboarding }), }; if (password && password.trim() !== "") { @@ -364,6 +369,8 @@ export async function PATCH(request: NextRequest) { weatherLocation: true, weatherRecentCities: true, viewSettings: true, + customCssVars: true, + customCss: true, hasCompletedOnboarding: true, } }); diff --git a/src/components/SettingsSidebar.tsx b/src/components/SettingsSidebar.tsx index 01e6958..6650794 100644 --- a/src/components/SettingsSidebar.tsx +++ b/src/components/SettingsSidebar.tsx @@ -145,6 +145,219 @@ function ExtraTimezonesEditor({ ); } +// All customizable color custom-properties defined in globals.css :root, grouped for the +// "Styling" tab's CSS Variables editor. Overrides are stored as a flat { "--var-name": "#hex" } +// map in profile.customCssVars and applied on top of containerStyle in WeeklyView. +const CSS_VARIABLE_GROUPS: { title: string; vars: { name: string; label: string; default: string }[] }[] = [ + { + title: "Base Colors", + vars: [ + { name: "--weekly-teal", label: "Accent", default: "#009a9a" }, + { name: "--weekly-bg", label: "Background", default: "#ffffff" }, + { name: "--weekly-text", label: "Text", default: "#000000" }, + { name: "--weekly-text-light", label: "Text (light)", default: "#767676" }, + { name: "--weekly-border", label: "Border", default: "#cccccc" }, + { name: "--weekly-completed", label: "Completed task", default: "#cccccc" }, + ], + }, + { + title: "Settings Sidebar", + vars: [ + { name: "--weekly-settings-bg", label: "Background", default: "#ffffff" }, + { name: "--weekly-settings-item-bg", label: "Item background", default: "#f8fafc" }, + { name: "--weekly-settings-label", label: "Label", default: "#475569" }, + { name: "--weekly-settings-title", label: "Title", default: "#111827" }, + { name: "--weekly-settings-input-bg", label: "Input background", default: "#ffffff" }, + { name: "--weekly-settings-input-border", label: "Input border", default: "#dddddd" }, + { name: "--weekly-settings-text", label: "Text", default: "#111827" }, + { name: "--weekly-settings-toggle-bg", label: "Toggle background", default: "#f3f4f6" }, + { name: "--weekly-settings-toggle-active-bg", label: "Toggle active background", default: "#ffffff" }, + { name: "--weekly-settings-toggle-text", label: "Toggle text", default: "#6b7280" }, + { name: "--weekly-settings-toggle-active-text", label: "Toggle active text", default: "#111827" }, + ], + }, + { + title: "Design Token Aliases", + vars: [ + { name: "--bg", label: "Background (alias)", default: "#ffffff" }, + { name: "--paper", label: "Paper (alias)", default: "#ffffff" }, + { name: "--ink", label: "Text (alias)", default: "#000000" }, + { name: "--ink-2", label: "Text variant 2", default: "#2a2a28" }, + { name: "--ink-3", label: "Text light (alias)", default: "#767676" }, + { name: "--ink-4", label: "Text variant 4", default: "#a8a8a2" }, + { name: "--ink-5", label: "Text variant 5", default: "#c8c8c2" }, + { name: "--accent", label: "Accent (alias)", default: "#009a9a" }, + { name: "--line", label: "Border (alias)", default: "#cccccc" }, + { name: "--line-soft", label: "Border (soft)", default: "#f3f3f0" }, + { name: "--weekend", label: "Weekend", default: "#dc2626" }, + { name: "--tasks-bg", label: "Tasks background", default: "#f4f2ee" }, + ], + }, + { + title: "Event Colors — Default", + vars: [ + { name: "--ev-default-bg", label: "Background", default: "#eef2f7" }, + { name: "--ev-default-border", label: "Border", default: "#6c87a8" }, + { name: "--ev-default-title", label: "Title", default: "#2b3f57" }, + { name: "--ev-default-meta", label: "Meta", default: "#6c87a8" }, + ], + }, + { + title: "Event Colors — Family", + vars: [ + { name: "--ev-fam-bg", label: "Background", default: "#fbeef0" }, + { name: "--ev-fam-border", label: "Border", default: "#b85a6a" }, + { name: "--ev-fam-title", label: "Title", default: "#5a2530" }, + { name: "--ev-fam-meta", label: "Meta", default: "#99536a" }, + ], + }, + { + title: "Event Colors — Finance", + vars: [ + { name: "--ev-fin-bg", label: "Background", default: "#f6f0e2" }, + { name: "--ev-fin-border", label: "Border", default: "#a88a3c" }, + { name: "--ev-fin-title", label: "Title", default: "#4a3a14" }, + { name: "--ev-fin-meta", label: "Meta", default: "#8a6f2a" }, + ], + }, + { + title: "Event Colors — Development", + vars: [ + { name: "--ev-dev-bg", label: "Background", default: "#ecf3ed" }, + { name: "--ev-dev-border", label: "Border", default: "#5a7a4a" }, + { name: "--ev-dev-title", label: "Title", default: "#2c4527" }, + { name: "--ev-dev-meta", label: "Meta", default: "#5a7a4a" }, + ], + }, + { + title: "All-Day Chips", + vars: [ + { name: "--chip-default-bg", label: "Default", default: "#b8b8b0" }, + { name: "--chip-fam-bg", label: "Family", default: "#e88a8a" }, + { name: "--chip-special-bg", label: "Special background", default: "#f4d588" }, + { name: "--chip-special-text", label: "Special text", default: "#6b4f10" }, + ], + }, +]; + +// Lets the user override any globals.css :root color variable individually. Overrides are +// merged into profile.customCssVars (a flat map) and spread onto the root container's inline +// style in WeeklyView, so they take precedence over every other color source (theme, per-field +// settings, etc.). +function CssVariablesEditor({ + profile, + setProfile, + saveSetting, +}: { + profile: any; + setProfile: React.Dispatch>; + saveSetting: (key: string, value: any) => void; +}) { + const de = profile.language === "de"; + const vars: Record = profile.customCssVars || {}; + const debounceTimer = useRef(null); + + const setVar = (name: string, value: string) => { + const next = { ...(profile.customCssVars || {}), [name]: value }; + setProfile((p: any) => ({ ...p, customCssVars: next })); + if (debounceTimer.current) clearTimeout(debounceTimer.current); + debounceTimer.current = setTimeout(() => saveSetting("customCssVars", next), 500); + }; + + const reset = () => { + setProfile((p: any) => ({ ...p, customCssVars: {} })); + saveSetting("customCssVars", {}); + }; + + return ( +
+
+ + +
+

+ {de + ? "Passe jede Design-Farbvariable des Stylesheets einzeln an." + : "Fine-tune every design-token color used by the stylesheet."} +

+ {CSS_VARIABLE_GROUPS.map((group) => ( +
+
+ {group.title} +
+
+ {group.vars.map((v) => ( +
+ + {v.label} + + setVar(v.name, e.target.value)} + style={{ + width: "32px", + height: "22px", + cursor: "pointer", + border: "1px solid var(--weekly-settings-input-border)", + borderRadius: "4px", + background: "transparent", + flexShrink: 0, + }} + /> +
+ ))} +
+
+ ))} +
+ ); +} + // Minimal ProjectIcon — resolves an icon name from the unified registry. function ProjectIcon({ icon, size = 18, color }: { icon?: string | null; size?: number; color?: string }) { if (!icon) return ; @@ -3712,6 +3925,55 @@ function SettingsSidebar({ + {/* CSS Variables */} + + + {/* Custom CSS */} +
+ +

+ {profile.language === "de" + ? "Wird nach dem Stylesheet geladen und kann jede Regel überschreiben." + : "Loaded after the stylesheet — can override any rule."} +

+