feat: weekday case setting, right-aligned icons, font matching, calendar fixes

- Add weekday case setting (Normal/Capitalize/Uppercase) with DB persistence
- Move provider icons to end of line (right-aligned with marginLeft: auto)
  in both GridTaskBlock and TaskItem
- Make time grid task font inherit from task font settings as fallback
  instead of separate hardcoded defaults (0.75rem/500 → task font values)
- Fix Jump to Date calendar: center-aligned dropdown, dark mode support
  via CSS variables, proper border and shadow styling
- Add weekdayCase to Prisma schema, profile API, and translations (EN/DE)

v1.19.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
mARTin 2026-03-08 13:23:22 +01:00
parent 05dab0720b
commit 9863f8e28e
6 changed files with 114 additions and 52 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "my-weekly-todo-list", "name": "my-weekly-todo-list",
"version": "1.18.0", "version": "1.19.0",
"description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -93,6 +93,7 @@ model User {
showCompletedTasks Boolean @default(true) showCompletedTasks Boolean @default(true)
showLines Boolean @default(true) showLines Boolean @default(true)
weekdayFormat String? @default("long") weekdayFormat String? @default("long")
weekdayCase String? @default("capitalize")
customWeekdayNames String? @default("") customWeekdayNames String? @default("")
startDayOffset Int @default(-1) startDayOffset Int @default(-1)
quoteSourceUrls String[] @default([]) quoteSourceUrls String[] @default([])

View File

@ -84,6 +84,7 @@ export async function GET(request: NextRequest) {
showLines: true, showLines: true,
startDayOffset: true, startDayOffset: true,
weekdayFormat: true, weekdayFormat: true,
weekdayCase: true,
customWeekdayNames: true, customWeekdayNames: true,
quoteSourceUrls: true, quoteSourceUrls: true,
accountNumber: true, accountNumber: true,
@ -128,7 +129,7 @@ export async function PATCH(request: NextRequest) {
cwFontFamily, cwFontSize, cwFontWeight, cwColor, cwFontFamily, cwFontSize, cwFontWeight, cwColor,
yearFontFamily, yearFontSize, yearFontWeight, yearColor, yearFontFamily, yearFontSize, yearFontWeight, yearColor,
showTaskCheckboxes, dayHeaderGap, showTaskCheckboxes, dayHeaderGap,
showCompletedTasks, showLines, startDayOffset, weekdayFormat, customWeekdayNames, quoteSourceUrls showCompletedTasks, showLines, startDayOffset, weekdayFormat, weekdayCase, customWeekdayNames, quoteSourceUrls
} = body; } = body;
const updateData: any = { const updateData: any = {
@ -202,6 +203,7 @@ export async function PATCH(request: NextRequest) {
...(showLines !== undefined && { showLines }), ...(showLines !== undefined && { showLines }),
...(startDayOffset !== undefined && { startDayOffset }), ...(startDayOffset !== undefined && { startDayOffset }),
...(weekdayFormat !== undefined && { weekdayFormat }), ...(weekdayFormat !== undefined && { weekdayFormat }),
...(weekdayCase !== undefined && { weekdayCase }),
...(customWeekdayNames !== undefined && { customWeekdayNames }), ...(customWeekdayNames !== undefined && { customWeekdayNames }),
...(quoteSourceUrls !== undefined && { quoteSourceUrls }), ...(quoteSourceUrls !== undefined && { quoteSourceUrls }),
}; };
@ -285,6 +287,7 @@ export async function PATCH(request: NextRequest) {
showLines: true, showLines: true,
startDayOffset: true, startDayOffset: true,
weekdayFormat: true, weekdayFormat: true,
weekdayCase: true,
customWeekdayNames: true, customWeekdayNames: true,
quoteSourceUrls: true, quoteSourceUrls: true,
accountNumber: true, accountNumber: true,

View File

@ -274,28 +274,6 @@ export function GridTaskBlock({
)} )}
<span style={task.completed && showTaskCheckboxes ? { opacity: 0.5, flex: 1, display: "flex", alignItems: "center", flexWrap: "wrap", rowGap: "2px" } : { flex: 1, display: "flex", alignItems: "center", flexWrap: "wrap", rowGap: "2px" }}> <span style={task.completed && showTaskCheckboxes ? { opacity: 0.5, flex: 1, display: "flex", alignItems: "center", flexWrap: "wrap", rowGap: "2px" } : { flex: 1, display: "flex", alignItems: "center", flexWrap: "wrap", rowGap: "2px" }}>
<span style={{ marginRight: "4px" }}>{task.title}</span> <span style={{ marginRight: "4px" }}>{task.title}</span>
{(() => {
const provider = task.externalProvider
|| (task.externalId?.startsWith("synology::") ? "synology" : null);
if (!provider) return null;
const iconMap: Record<string, { icon: any; color: string; label: string }> = {
google: { icon: faGoogle, color: "#4285F4", label: "Google" },
outlook: { icon: faMicrosoft, color: "#0078D4", label: "Microsoft" },
apple: { icon: faApple, color: "#555", label: "Apple" },
synology: { icon: faServer, color: "#007AFF", label: "Synology" },
};
const info = iconMap[provider];
if (!info) return null;
return (
<span
className="flex-shrink-0"
title={`Synced with ${info.label}`}
style={{ display: "inline-flex", alignItems: "center", opacity: 0.55 }}
>
<FontAwesomeIcon icon={info.icon} style={{ width: 10, height: 10, color: info.color }} />
</span>
);
})()}
{/* Subtask indicator */} {/* Subtask indicator */}
{task.subTasks && task.subTasks.length > 0 && (() => { {task.subTasks && task.subTasks.length > 0 && (() => {
const completed = task.subTasks.filter(s => s.completed).length; const completed = task.subTasks.filter(s => s.completed).length;
@ -362,6 +340,28 @@ export function GridTaskBlock({
</span> </span>
</span> </span>
)} )}
{(() => {
const provider = task.externalProvider
|| (task.externalId?.startsWith("synology::") ? "synology" : null);
if (!provider) return null;
const iconMap: Record<string, { icon: any; color: string; label: string }> = {
google: { icon: faGoogle, color: "#4285F4", label: "Google" },
outlook: { icon: faMicrosoft, color: "#0078D4", label: "Microsoft" },
apple: { icon: faApple, color: "#555", label: "Apple" },
synology: { icon: faServer, color: "#007AFF", label: "Synology" },
};
const info = iconMap[provider];
if (!info) return null;
return (
<span
className="flex-shrink-0"
title={`Synced with ${info.label}`}
style={{ display: "inline-flex", alignItems: "center", opacity: 0.55, marginLeft: "auto", paddingLeft: "4px" }}
>
<FontAwesomeIcon icon={info.icon} style={{ width: 10, height: 10, color: info.color }} />
</span>
);
})()}
</div> </div>
<div <div

View File

@ -26,13 +26,13 @@ export default function SimpleDatePicker({ selected, onSelect, onClose, language
const renderHeader = () => ( const renderHeader = () => (
<div className="datepicker-header" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '1rem', padding: '0 0.5rem' }}> <div className="datepicker-header" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: '1rem', padding: '0 0.5rem' }}>
<button onClick={() => setCurrentMonth(subMonths(currentMonth, 1))} style={{ padding: '0.25rem', background: 'none', border: 'none', cursor: 'pointer', color: '#9ca3af', transition: 'color 0.2s' }}> <button onClick={() => setCurrentMonth(subMonths(currentMonth, 1))} style={{ padding: '0.25rem', background: 'none', border: 'none', cursor: 'pointer', color: 'var(--weekly-text-light, #9ca3af)', transition: 'color 0.2s' }}>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="15 18 9 12 15 6"></polyline></svg>
</button> </button>
<div style={{ fontWeight: 'bold', fontSize: '0.875rem', letterSpacing: '0.05em', textTransform: 'uppercase', color: '#374151' }}> <div style={{ fontWeight: 'bold', fontSize: '0.875rem', letterSpacing: '0.05em', textTransform: 'uppercase', color: 'var(--weekly-text, #374151)' }}>
{format(currentMonth, 'MMMM yyyy', { locale })} {format(currentMonth, 'MMMM yyyy', { locale })}
</div> </div>
<button onClick={() => setCurrentMonth(addMonths(currentMonth, 1))} style={{ padding: '0.25rem', background: 'none', border: 'none', cursor: 'pointer', color: '#9ca3af', transition: 'color 0.2s' }}> <button onClick={() => setCurrentMonth(addMonths(currentMonth, 1))} style={{ padding: '0.25rem', background: 'none', border: 'none', cursor: 'pointer', color: 'var(--weekly-text-light, #9ca3af)', transition: 'color 0.2s' }}>
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg> <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6"></polyline></svg>
</button> </button>
</div> </div>
@ -43,7 +43,7 @@ export default function SimpleDatePicker({ selected, onSelect, onClose, language
const startDate = startOfWeek(currentMonth, { weekStartsOn: 1 }); const startDate = startOfWeek(currentMonth, { weekStartsOn: 1 });
for (let i = 0; i < 7; i++) { for (let i = 0; i < 7; i++) {
days.push( days.push(
<div key={i} style={{ textAlign: 'center', fontSize: '0.75rem', fontWeight: 'bold', color: '#9ca3af', padding: '0.5rem 0' }}> <div key={i} style={{ textAlign: 'center', fontSize: '0.75rem', fontWeight: 'bold', color: 'var(--weekly-text-light, #9ca3af)', padding: '0.5rem 0' }}>
{format(addDays(startDate, i), 'EEEEEE', { locale })} {format(addDays(startDate, i), 'EEEEEE', { locale })}
</div> </div>
); );
@ -87,7 +87,7 @@ export default function SimpleDatePicker({ selected, onSelect, onClose, language
cursor: 'pointer', cursor: 'pointer',
transition: 'all 0.2s', transition: 'all 0.2s',
margin: '0 auto', margin: '0 auto',
color: !isCurrentMonth ? '#d1d5db' : isDaySelected ? 'white' : '#374151', color: !isCurrentMonth ? 'var(--weekly-text-light, #d1d5db)' : isDaySelected ? 'white' : 'var(--weekly-text, #374151)',
background: isDaySelected ? 'black' : 'transparent', background: isDaySelected ? 'black' : 'transparent',
fontWeight: isDaySelected ? 'bold' : 'normal', fontWeight: isDaySelected ? 'bold' : 'normal',
boxShadow: isDaySelected ? '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)' : 'none', boxShadow: isDaySelected ? '0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06)' : 'none',
@ -95,7 +95,7 @@ export default function SimpleDatePicker({ selected, onSelect, onClose, language
...(isToday && !isDaySelected ? { color: '#ef4444', fontWeight: 'bold' } : {}) ...(isToday && !isDaySelected ? { color: '#ef4444', fontWeight: 'bold' } : {})
}} }}
onMouseEnter={(e) => { onMouseEnter={(e) => {
if (!isDaySelected) e.currentTarget.style.backgroundColor = '#f3f4f6'; if (!isDaySelected) e.currentTarget.style.backgroundColor = 'var(--weekly-hover-bg, #f3f4f6)';
}} }}
onMouseLeave={(e) => { onMouseLeave={(e) => {
if (!isDaySelected) e.currentTarget.style.backgroundColor = 'transparent'; if (!isDaySelected) e.currentTarget.style.backgroundColor = 'transparent';
@ -120,28 +120,30 @@ export default function SimpleDatePicker({ selected, onSelect, onClose, language
<div ref={modalRef} style={{ <div ref={modalRef} style={{
position: 'absolute', position: 'absolute',
top: '100%', top: '100%',
right: 0, left: '50%',
transform: 'translateX(-50%)',
marginTop: '0.5rem', marginTop: '0.5rem',
backgroundColor: 'white', backgroundColor: 'var(--weekly-bg, white)',
borderRadius: '0.5rem', borderRadius: '0.5rem',
boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.15), 0 10px 10px -5px rgba(0, 0, 0, 0.08)',
padding: '1rem', padding: '1rem',
zIndex: 2000, zIndex: 2000,
width: '18rem', width: '18rem',
border: '1px solid #f3f4f6', border: '1px solid var(--weekly-border, #e5e7eb)',
animation: 'fadeIn 0.15s ease-out' animation: 'fadeIn 0.15s ease-out'
}}> }}>
{/* Decorative triangle */} {/* Decorative triangle */}
<div style={{ <div style={{
position: 'absolute', position: 'absolute',
top: '-0.3rem', top: '-0.3rem',
right: '1rem', left: '50%',
marginLeft: '-0.375rem',
width: '0.75rem', width: '0.75rem',
height: '0.75rem', height: '0.75rem',
backgroundColor: 'white', backgroundColor: 'var(--weekly-bg, white)',
transform: 'rotate(45deg)', transform: 'rotate(45deg)',
borderTop: '1px solid #f3f4f6', borderTop: '1px solid var(--weekly-border, #e5e7eb)',
borderLeft: '1px solid #f3f4f6' borderLeft: '1px solid var(--weekly-border, #e5e7eb)'
}}></div> }}></div>
{renderHeader()} {renderHeader()}
{renderDays()} {renderDays()}

View File

@ -317,6 +317,10 @@ const translations: Record<string, any> = {
weekdayFormatCustom: "Custom", weekdayFormatCustom: "Custom",
customWeekdayNamesMon: "Mo; Tu; We; Th; Fr; Sa; Su", customWeekdayNamesMon: "Mo; Tu; We; Th; Fr; Sa; Su",
customWeekdayNamesSun: "Su; Mo; Tu; We; Th; Fr; Sa", customWeekdayNamesSun: "Su; Mo; Tu; We; Th; Fr; Sa",
weekdayCase: "Weekday Case",
weekdayCaseNormal: "Normal (monday)",
weekdayCaseCapitalize: "Capitalize (Monday)",
weekdayCaseUppercase: "Uppercase (MONDAY)",
}, },
de: { de: {
settings: "Einstellungen", settings: "Einstellungen",
@ -417,6 +421,10 @@ const translations: Record<string, any> = {
weekdayFormatCustom: "Benutzerdefiniert", weekdayFormatCustom: "Benutzerdefiniert",
customWeekdayNamesMon: "Mo; Di; Mi; Do; Fr; Sa; So", customWeekdayNamesMon: "Mo; Di; Mi; Do; Fr; Sa; So",
customWeekdayNamesSun: "So; Mo; Di; Mi; Do; Fr; Sa", customWeekdayNamesSun: "So; Mo; Di; Mi; Do; Fr; Sa",
weekdayCase: "Groß-/Kleinschreibung",
weekdayCaseNormal: "Klein (montag)",
weekdayCaseCapitalize: "Großbuchstabe (Montag)",
weekdayCaseUppercase: "Großbuchstaben (MONTAG)",
}, },
}; };
@ -448,23 +456,30 @@ function formatDateHeader(date: Date, locale: string = "en-US"): string {
return date.toLocaleDateString(locale, { day: "numeric", month: "short" }); // e.g. 12. Feb. return date.toLocaleDateString(locale, { day: "numeric", month: "short" }); // e.g. 12. Feb.
} }
function getDayName(date: Date, locale: string = "en-US", format?: string, customNames?: string, weekStartDay: number = 0): string { function getDayName(date: Date, locale: string = "en-US", format?: string, customNames?: string, weekStartDay: number = 0, dayCase: string = "capitalize"): string {
let name: string;
if (format === "custom" && customNames) { if (format === "custom" && customNames) {
// Split by comma or semicolon to allow spaces in names // Split by comma or semicolon to allow spaces in names
const names = customNames.split(/[,;]+/).map(s => s.trim()).filter(Boolean); const names = customNames.split(/[,;]+/).map(s => s.trim()).filter(Boolean);
if (names.length === 7) { if (names.length === 7) {
// Adjust index based on weekStartDay (0=Sun, 1=Mon) // Adjust index based on weekStartDay (0=Sun, 1=Mon)
const index = (date.getDay() - weekStartDay + 7) % 7; const index = (date.getDay() - weekStartDay + 7) % 7;
return names[index].toUpperCase(); name = names[index];
} else {
name = date.toLocaleDateString(locale, { weekday: "long" });
}
} else {
const weekdayOption = format === "narrow" ? "narrow" : (format === "short" ? "short" : "long");
try {
name = date.toLocaleDateString(locale, { weekday: weekdayOption });
} catch (e) {
name = date.toLocaleDateString("en-US", { weekday: weekdayOption });
} }
} }
if (dayCase === "uppercase") return name.toUpperCase();
const weekdayOption = format === "narrow" ? "narrow" : (format === "short" ? "short" : "long"); if (dayCase === "normal") return name.toLowerCase();
try { // capitalize: first letter uppercase, rest lowercase
return date.toLocaleDateString(locale, { weekday: weekdayOption }).toUpperCase(); return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
} catch (e) {
return date.toLocaleDateString("en-US", { weekday: weekdayOption }).toUpperCase();
}
} }
function isSameDay(d1: Date, d2: Date): boolean { function isSameDay(d1: Date, d2: Date): boolean {
@ -804,6 +819,7 @@ export default function WeeklyView() {
quoteSourceUrls?: string[]; quoteSourceUrls?: string[];
startDayOffset?: number; startDayOffset?: number;
weekdayFormat?: "long" | "short" | "narrow" | "custom"; weekdayFormat?: "long" | "short" | "narrow" | "custom";
weekdayCase?: "normal" | "capitalize" | "uppercase";
customWeekdayNames?: string; customWeekdayNames?: string;
dateVerticalAlign?: "top" | "middle" | "bottom"; dateVerticalAlign?: "top" | "middle" | "bottom";
}>({ }>({
@ -921,6 +937,7 @@ export default function WeeklyView() {
const [showSchedule, setShowSchedule] = useState(true); const [showSchedule, setShowSchedule] = useState(true);
const [focusBreakDuration, setFocusBreakDuration] = useState(5); const [focusBreakDuration, setFocusBreakDuration] = useState(5);
const [weekdayFormat, setWeekdayFormat] = useState<"long" | "short" | "narrow" | "custom">("long"); const [weekdayFormat, setWeekdayFormat] = useState<"long" | "short" | "narrow" | "custom">("long");
const [weekdayCase, setWeekdayCase] = useState<"normal" | "capitalize" | "uppercase">("capitalize");
const [customWeekdayNames, setCustomWeekdayNames] = useState(""); const [customWeekdayNames, setCustomWeekdayNames] = useState("");
// New UI State // New UI State
@ -1594,6 +1611,7 @@ export default function WeeklyView() {
setShowAllDay(newSettings.showAllDayEvents); setShowAllDay(newSettings.showAllDayEvents);
setShowSchedule(newSettings.showSchedule); setShowSchedule(newSettings.showSchedule);
if (newSettings.weekdayFormat) setWeekdayFormat(newSettings.weekdayFormat); if (newSettings.weekdayFormat) setWeekdayFormat(newSettings.weekdayFormat);
if (newSettings.weekdayCase) setWeekdayCase(newSettings.weekdayCase);
if (newSettings.customWeekdayNames !== undefined) setCustomWeekdayNames(newSettings.customWeekdayNames); if (newSettings.customWeekdayNames !== undefined) setCustomWeekdayNames(newSettings.customWeekdayNames);
setHeadlineFont(newSettings.headlineFont); setHeadlineFont(newSettings.headlineFont);
setHeadlineFontSize(newSettings.headlineFontSize); setHeadlineFontSize(newSettings.headlineFontSize);
@ -1701,6 +1719,8 @@ export default function WeeklyView() {
setShowSchedule(data.user.showSchedule); setShowSchedule(data.user.showSchedule);
if (data.user.weekdayFormat) if (data.user.weekdayFormat)
setWeekdayFormat(data.user.weekdayFormat as any); setWeekdayFormat(data.user.weekdayFormat as any);
if (data.user.weekdayCase)
setWeekdayCase(data.user.weekdayCase as any);
if (data.user.customWeekdayNames) if (data.user.customWeekdayNames)
setCustomWeekdayNames(data.user.customWeekdayNames); setCustomWeekdayNames(data.user.customWeekdayNames);
if (data.user.hourLabelFormat) if (data.user.hourLabelFormat)
@ -3779,9 +3799,11 @@ export default function WeeklyView() {
"--weekly-date-weight": profile.dateFontWeight || "400", "--weekly-date-weight": profile.dateFontWeight || "400",
"--weekly-time-task-font": fontVal(profile.timeTaskFontFamily) "--weekly-time-task-font": fontVal(profile.timeTaskFontFamily)
? `"${fontVal(profile.timeTaskFontFamily)}", sans-serif` ? `"${fontVal(profile.timeTaskFontFamily)}", sans-serif`
: "var(--weekly-font)", : fontVal(profile.taskFontFamily)
"--weekly-time-task-size": scaleRem(profile.timeTaskFontSize || "0.75rem"), ? `"${fontVal(profile.taskFontFamily)}", sans-serif`
"--weekly-time-task-weight": profile.timeTaskFontWeight || "500", : "var(--weekly-font)",
"--weekly-time-task-size": scaleRem(profile.timeTaskFontSize || profile.taskFontSize || "0.9rem"),
"--weekly-time-task-weight": profile.timeTaskFontWeight || profile.taskFontWeight || "400",
"--weekly-font": "--weekly-font":
"var(--font-body)" /* Force default body font as requested */, "var(--font-body)" /* Force default body font as requested */,
"--weekly-task-font": fontVal(profile.taskFontFamily) "--weekly-task-font": fontVal(profile.taskFontFamily)
@ -4927,7 +4949,7 @@ export default function WeeklyView() {
className={`weekly-day-name ${isSameDay(date, new Date()) ? "is-today" : ""}`} className={`weekly-day-name ${isSameDay(date, new Date()) ? "is-today" : ""}`}
style={{ marginBottom: 0, flexShrink: 0 }} style={{ marginBottom: 0, flexShrink: 0 }}
> >
{getDayName(date, language, weekdayFormat, customWeekdayNames, weekStartDay)} {getDayName(date, language, weekdayFormat, customWeekdayNames, weekStartDay, weekdayCase)}
</h3> </h3>
{(activeDateLayout === "right" || {(activeDateLayout === "right" ||
activeDateLayout === "above" || activeDateLayout === "above" ||
@ -7085,7 +7107,7 @@ function TaskItem({
<span <span
className="flex-shrink-0" className="flex-shrink-0"
title={`Synced with ${info.label}`} title={`Synced with ${info.label}`}
style={{ display: "inline-flex", alignItems: "center", opacity: 0.55, marginLeft: "4px" }} style={{ display: "inline-flex", alignItems: "center", opacity: 0.55, marginLeft: "auto", paddingLeft: "4px" }}
> >
<FontAwesomeIcon icon={info.icon} style={{ width: 12, height: 12, color: info.color }} /> <FontAwesomeIcon icon={info.icon} style={{ width: 12, height: 12, color: info.color }} />
</span> </span>
@ -7709,6 +7731,7 @@ interface SettingsSidebarProps {
dateLayout?: "above" | "below" | "left" | "right" | "hidden"; dateLayout?: "above" | "below" | "left" | "right" | "hidden";
mobileDateLayout?: "above" | "below" | "left" | "right" | "hidden"; mobileDateLayout?: "above" | "below" | "left" | "right" | "hidden";
weekdayFormat?: "long" | "short" | "narrow" | "custom"; weekdayFormat?: "long" | "short" | "narrow" | "custom";
weekdayCase?: "normal" | "capitalize" | "uppercase";
customWeekdayNames?: string; customWeekdayNames?: string;
dateAlignment?: "left" | "center" | "right" | "tight"; dateAlignment?: "left" | "center" | "right" | "tight";
hourLabelFormat: "short" | "full"; hourLabelFormat: "short" | "full";
@ -8083,6 +8106,7 @@ function SettingsSidebar({
startDayOffset?: number; startDayOffset?: number;
id?: string; id?: string;
weekdayFormat?: "long" | "short" | "narrow" | "custom"; weekdayFormat?: "long" | "short" | "narrow" | "custom";
weekdayCase?: "normal" | "capitalize" | "uppercase";
customWeekdayNames?: string; customWeekdayNames?: string;
dateVerticalAlign?: "top" | "middle" | "bottom"; dateVerticalAlign?: "top" | "middle" | "bottom";
}>({ }>({
@ -8141,6 +8165,7 @@ function SettingsSidebar({
todayHighlightColor: "#f0fafa", todayHighlightColor: "#f0fafa",
pastDayColor: "#a6a6a7", pastDayColor: "#a6a6a7",
weekdayFormat: "long", weekdayFormat: "long",
weekdayCase: "capitalize",
customWeekdayNames: "", customWeekdayNames: "",
}); });
@ -8252,6 +8277,7 @@ function SettingsSidebar({
showTaskCheckboxes: profile.showTaskCheckboxes, showTaskCheckboxes: profile.showTaskCheckboxes,
startDayOffset: profile.startDayOffset, startDayOffset: profile.startDayOffset,
weekdayFormat: profile.weekdayFormat, weekdayFormat: profile.weekdayFormat,
weekdayCase: profile.weekdayCase,
customWeekdayNames: profile.customWeekdayNames, customWeekdayNames: profile.customWeekdayNames,
} as any); } as any);
}, [profile, showTimeGrid, cellDuration, viewStyle, fontSize, showNextTask, showSomeday, showAllDay, showSchedule]); }, [profile, showTimeGrid, cellDuration, viewStyle, fontSize, showNextTask, showSomeday, showAllDay, showSchedule]);
@ -8290,6 +8316,7 @@ function SettingsSidebar({
: true, : true,
cellDuration: data.user.cellDuration || 30, cellDuration: data.user.cellDuration || 30,
weekdayFormat: data.user.weekdayFormat || "long", weekdayFormat: data.user.weekdayFormat || "long",
weekdayCase: data.user.weekdayCase || "capitalize",
customWeekdayNames: data.user.customWeekdayNames || "", customWeekdayNames: data.user.customWeekdayNames || "",
viewStyle: data.user.viewStyle || "list", viewStyle: data.user.viewStyle || "list",
fontSize: data.user.fontSize || "M", fontSize: data.user.fontSize || "M",
@ -9466,6 +9493,35 @@ function SettingsSidebar({
</div> </div>
</div> </div>
{/* Weekday Case */}
<div style={{ marginTop: "8px" }}>
<label
style={{
display: "block",
fontSize: "0.9rem",
fontWeight: 600,
marginBottom: "8px",
}}
>
{t.weekdayCase || "Weekday Case"}
</label>
<select
value={profile.weekdayCase || "capitalize"}
onChange={(e) =>
setProfile({
...profile,
weekdayCase: e.target.value as "normal" | "capitalize" | "uppercase",
})
}
className="weekly-input"
style={{ width: "100%", padding: "8px", fontSize: "0.9rem", border: "1px solid var(--weekly-settings-input-border)", borderRadius: "4px", background: "var(--weekly-settings-input-bg)", color: "var(--weekly-settings-text)" }}
>
<option value="normal">{t.weekdayCaseNormal || "Normal (monday)"}</option>
<option value="capitalize">{t.weekdayCaseCapitalize || "Capitalize (Monday)"}</option>
<option value="uppercase">{t.weekdayCaseUppercase || "Uppercase (MONDAY)"}</option>
</select>
</div>
<div style={{ marginTop: "8px" }}> <div style={{ marginTop: "8px" }}>
<label <label
style={{ style={{