Implement custom header formatting (v1.33.0)

This commit is contained in:
mARTin 2026-03-13 15:18:32 +01:00
parent 889282381f
commit b827f39722
2 changed files with 82 additions and 4 deletions

View File

@ -1,6 +1,6 @@
{
"name": "my-weekly-todo-list",
"version": "1.32.0",
"version": "1.33.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": {

View File

@ -263,6 +263,8 @@ const translations: Record<string, any> = {
headerDisplayMonth: "Month Name",
headerDisplayMonthYear: "Month & Year",
headerDisplayDate: "Full Date",
headerDisplayCustom: "Custom",
headerCustomFormatLabel: "Format string (e.g. DD.MM.YYYY)",
language: "Language",
dateFormat: "Date Format",
timeFormat: "Time Format",
@ -471,6 +473,8 @@ const translations: Record<string, any> = {
headerDisplayMonth: "Monatsname",
headerDisplayMonthYear: "Monat & Jahr",
headerDisplayDate: "Vollständiges Datum",
headerDisplayCustom: "Benutzerdefiniert",
headerCustomFormatLabel: "Format (z.B. DD.MM.YYYY)",
listView: "Liste",
notes: "Notizen",
notesSidebar: "Notizen-Seitenleiste",
@ -682,6 +686,8 @@ const translations: Record<string, any> = {
headerDisplayMonth: "Nom du mois",
headerDisplayMonthYear: "Mois & Année",
headerDisplayDate: "Date complète",
headerDisplayCustom: "Personnalisé",
headerCustomFormatLabel: "Format (ex: DD.MM.YYYY)",
language: "Langue",
dateFormat: "Format de date",
timeFormat: "Format d'heure",
@ -891,6 +897,8 @@ const translations: Record<string, any> = {
headerDisplayMonth: "Nombre del mes",
headerDisplayMonthYear: "Mes y Año",
headerDisplayDate: "Fecha completa",
headerDisplayCustom: "Personalizado",
headerCustomFormatLabel: "Formato (ej. DD.MM.YYYY)",
language: "Idioma",
dateFormat: "Formato de fecha",
timeFormat: "Formato de hora",
@ -1100,6 +1108,8 @@ const translations: Record<string, any> = {
headerDisplayMonth: "Nome del mese",
headerDisplayMonthYear: "Mese e Anno",
headerDisplayDate: "Data completa",
headerDisplayCustom: "Personalizzato",
headerCustomFormatLabel: "Formato (es. DD.MM.YYYY)",
language: "Lingua",
dateFormat: "Formato data",
timeFormat: "Formato ora",
@ -1424,6 +1434,37 @@ function getCWReferenceDate(days: Date[]): Date {
return new Date(first.getTime() - distToMonday * 86400000);
}
// Format a custom header string using tokens
function formatCustomHeader(format: string, days: Date[], language: string, t: any): string {
if (!format) return "";
const refDate = getCWReferenceDate(days);
const today = new Date();
let result = format;
// [TODAY] token
result = result.replace(/\[TODAY\]/g, today.toLocaleDateString(language, { day: '2-digit', month: '2-digit', year: 'numeric' }));
// YYYY
result = result.replace(/YYYY/g, refDate.getFullYear().toString());
// WW (ISO Week)
result = result.replace(/WW/g, getWeekNumber(refDate).toString().padStart(2, '0'));
result = result.replace(/W/g, getWeekNumber(refDate).toString());
// MMMM (Month Name Long)
result = result.replace(/MMMM/g, refDate.toLocaleDateString(language, { month: 'long' }));
result = result.replace(/MMM/g, refDate.toLocaleDateString(language, { month: 'short' }));
result = result.replace(/MM/g, (refDate.getMonth() + 1).toString().padStart(2, '0'));
result = result.replace(/M/g, (refDate.getMonth() + 1).toString());
// DD (Day)
result = result.replace(/DD/g, refDate.getDate().toString().padStart(2, '0'));
result = result.replace(/D/g, refDate.getDate().toString());
return result;
}
// Check if an event is an all-day event
// Defined outside component to avoid stale closure issues in useCallbacks
const isAllDayEvent = (event: CalendarEvent): boolean => {
@ -1803,7 +1844,8 @@ export default function WeeklyView() {
weekdayCase?: "normal" | "capitalize" | "uppercase";
customWeekdayNames?: string;
dateVerticalAlign?: "top" | "middle" | "bottom";
headerDisplay?: "kw" | "month" | "month_year" | "date";
headerDisplay?: "kw" | "month" | "month_year" | "date" | "custom";
headerCustomFormat?: string;
lightTheme?: any;
darkTheme?: any;
}>({
@ -5474,6 +5516,8 @@ export default function WeeklyView() {
<span>{getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { day: '2-digit', month: '2-digit', year: 'numeric' })}</span>
) : profile.headerDisplay === "month_year" ? (
<span>{getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { month: 'long', year: 'numeric' })}</span>
) : profile.headerDisplay === "custom" ? (
<span>{formatCustomHeader(profile.headerCustomFormat || "KW WW | YYYY", getVisibleDays(), language, t)}</span>
) : (
<>
<span>KW{getWeekNumber(getCWReferenceDate(getVisibleDays())).toString().padStart(2, "0")}</span>
@ -5731,6 +5775,8 @@ export default function WeeklyView() {
? getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { month: "long", year: "numeric" })
: profile.headerDisplay === "date"
? getCWReferenceDate(getVisibleDays()).toLocaleDateString(language, { day: '2-digit', month: '2-digit', year: 'numeric' })
: profile.headerDisplay === "custom"
? formatCustomHeader(profile.headerCustomFormat || "KW WW | YYYY", getVisibleDays(), language, t)
: `KW ${getWeekNumber(getCWReferenceDate(getVisibleDays())).toString().padStart(2, "0")}`
}
</span>
@ -9694,7 +9740,8 @@ function SettingsSidebar({
weekdayCase?: "normal" | "capitalize" | "uppercase";
customWeekdayNames?: string;
dateVerticalAlign?: "top" | "middle" | "bottom";
headerDisplay?: "kw" | "month" | "month_year" | "date";
headerDisplay?: "kw" | "month" | "month_year" | "date" | "custom";
headerCustomFormat?: string;
}>({
name: "",
email: "",
@ -9709,6 +9756,7 @@ function SettingsSidebar({
focusTimerDuration: 25,
focusBreakDuration: 5,
headerDisplay: "kw",
headerCustomFormat: "KW WW | YYYY",
cellDuration: 30,
viewStyle: "list",
fontSize: "M",
@ -10852,7 +10900,37 @@ function SettingsSidebar({
>
{t.headerDisplayDate}
</button>
<button
onClick={() => {
setProfile({ ...profile, headerDisplay: "custom" });
saveSetting("headerDisplay", "custom");
}}
className={`px-4 py-2 text-sm rounded transition-colors ${profile.headerDisplay === "custom" ? "bg-white shadow-sm font-bold text-black" : "text-gray-500 hover:text-gray-900 hover:bg-gray-200 dark:hover:bg-gray-700"}`}
>
{t.headerDisplayCustom}
</button>
</div>
{profile.headerDisplay === "custom" && (
<div className="mt-2">
<label style={{ fontSize: "0.75rem", color: "var(--weekly-settings-label)", display: "block", marginBottom: "4px" }}>
{t.headerCustomFormatLabel}
</label>
<input
type="text"
value={profile.headerCustomFormat || ""}
onChange={(e) => {
const val = e.target.value;
setProfile({ ...profile, headerCustomFormat: val });
saveSetting("headerCustomFormat", val);
}}
placeholder="KW WW | YYYY or DD.MM.YYYY"
className="w-full px-3 py-1.5 text-sm rounded border border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white"
/>
<div className="mt-1 text-[10px] text-gray-500 leading-tight">
Tokens: WW (Week), YYYY (Year), MMMM (Month Name), MM (Month Num), DD (Day), [TODAY] (Active Date)
</div>
</div>
)}
</div>
{/* Kanban Stages Settings */}