diff --git a/package.json b/package.json index fb41a91..d6d03b1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.70.0", + "version": "1.71.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/src/app/globals.css b/src/app/globals.css index d305128..901c5f3 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -3177,6 +3177,14 @@ h3 { flex-shrink: 0; } +.all-day-events-section.collapsed { + height: 24px !important; + min-height: 24px; + max-height: 24px; + overflow: hidden; + padding: 0; +} + /* Resize handle for draggable section borders */ .resize-handle { height: 7px; @@ -4120,6 +4128,15 @@ h3 { opacity: 0.9; } +.event-location-row { + font-family: var(--weekly-event-font); + font-size: 0.65rem; + opacity: 0.7; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + /* Header Icons */ .weekly-btn-icon { display: flex; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index fe62630..6ff6e62 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -3,7 +3,7 @@ import { Inter } from 'next/font/google'; import './globals.css'; import { Providers } from './providers'; -const inter = Inter({ subsets: ['latin'] }); +const inter = Inter({ subsets: ['latin', 'latin-ext'] }); export const viewport: Viewport = { width: 'device-width', diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index 210ba58..4cb4dce 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -245,7 +245,7 @@ const useGoogleFonts = (fonts: string[]) => { let link = document.getElementById(linkId) as HTMLLinkElement; const fontQuery = fontsToLoad.map((f) => f.replace(" ", "+")).join("|"); - const href = `https://fonts.googleapis.com/css2?family=${fontsToLoad.map((f) => `${f.replace(" ", "+")}:wght@300;400;500;700`).join("&family=")}&display=swap`; + const href = `https://fonts.googleapis.com/css2?family=${fontsToLoad.map((f) => `${f.replace(" ", "+")}:wght@300;400;500;700`).join("&family=")}&subset=latin,latin-ext&display=swap`; if (!link) { link = document.createElement("link"); @@ -2780,7 +2780,9 @@ export default function WeeklyView() { if (!res.ok) continue; const contentType = res.headers.get("content-type") || ""; if (!contentType.includes("application/json")) continue; - const data = await res.json(); + // Ensure proper UTF-8 decoding for quotes with special characters + const rawText = await res.text(); + const data = JSON.parse(rawText); let quoteText = ""; if (Array.isArray(data) && data.length > 0) { @@ -7861,6 +7863,9 @@ export default function WeeklyView() {
{timeStr}
+ {event.location && ( +
{event.location}
+ )} {(event.isRecurring || event.description || profile.showCalendarProviderIcon) && (
{event.description && ( @@ -8002,6 +8007,9 @@ export default function WeeklyView() { > {event.title}
+ {event.location && ( +
{event.location}
+ )} {(event.isRecurring || event.description || profile.showCalendarProviderIcon) && (
{event.description && ( diff --git a/src/lib/apple-calendar.ts b/src/lib/apple-calendar.ts index edd557a..2c28fb4 100644 --- a/src/lib/apple-calendar.ts +++ b/src/lib/apple-calendar.ts @@ -701,25 +701,32 @@ export const updateEvent = async ( const existingDtStart = vevent.getFirstProperty('dtstart'); const existingTzid = existingDtStart?.getParameter('tzid') as string | undefined; + // Helper to convert UTC dateTime to local ICAL.Time preserving TZID + const toLocalIcalTime = (dateTimeStr: string, tzid: string) => { + const d = new Date(dateTimeStr); + const parts = new Intl.DateTimeFormat('en-CA', { + timeZone: tzid, year: 'numeric', month: '2-digit', day: '2-digit', + hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, + }).formatToParts(d); + const get = (t: string) => parts.find(p => p.type === t)?.value || '00'; + const tz = ICAL.Timezone.fromData({ tzid, component: comp.getFirstSubcomponent('vtimezone') || undefined }); + return ICAL.Time.fromData({ + year: parseInt(get('year')), month: parseInt(get('month')), day: parseInt(get('day')), + hour: parseInt(get('hour')), minute: parseInt(get('minute')), second: parseInt(get('second')), + isDate: false, + }, tz); + }; + if (eventData.start) { if (eventData.start.date) { event.startDate = ICAL.Time.fromJSDate(new Date(eventData.start.date), true); event.startDate.isDate = true; } else if (eventData.start.dateTime) { if (existingTzid) { - // Convert UTC dateTime to local time in the TZID - const d = new Date(eventData.start.dateTime); - const parts = new Intl.DateTimeFormat('en-CA', { - timeZone: existingTzid, year: 'numeric', month: '2-digit', day: '2-digit', - hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, - }).formatToParts(d); - const get = (t: string) => parts.find(p => p.type === t)?.value || '00'; - const localTime = ICAL.Time.fromData({ - year: parseInt(get('year')), month: parseInt(get('month')), day: parseInt(get('day')), - hour: parseInt(get('hour')), minute: parseInt(get('minute')), second: parseInt(get('second')), - isDate: false, - }); - event.startDate = localTime; + event.startDate = toLocalIcalTime(eventData.start.dateTime, existingTzid); + // Ensure TZID parameter is preserved on the property + const dtStartProp = vevent.getFirstProperty('dtstart'); + if (dtStartProp) dtStartProp.setParameter('tzid', existingTzid); } else { event.startDate = ICAL.Time.fromJSDate(new Date(eventData.start.dateTime), true); event.startDate.isDate = false; @@ -733,18 +740,9 @@ export const updateEvent = async ( event.endDate.isDate = true; } else if (eventData.end.dateTime) { if (existingTzid) { - const d = new Date(eventData.end.dateTime); - const parts = new Intl.DateTimeFormat('en-CA', { - timeZone: existingTzid, year: 'numeric', month: '2-digit', day: '2-digit', - hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, - }).formatToParts(d); - const get = (t: string) => parts.find(p => p.type === t)?.value || '00'; - const localTime = ICAL.Time.fromData({ - year: parseInt(get('year')), month: parseInt(get('month')), day: parseInt(get('day')), - hour: parseInt(get('hour')), minute: parseInt(get('minute')), second: parseInt(get('second')), - isDate: false, - }); - event.endDate = localTime; + event.endDate = toLocalIcalTime(eventData.end.dateTime, existingTzid); + const dtEndProp = vevent.getFirstProperty('dtend'); + if (dtEndProp) dtEndProp.setParameter('tzid', existingTzid); } else { event.endDate = ICAL.Time.fromJSDate(new Date(eventData.end.dateTime), true); event.endDate.isDate = false; @@ -838,12 +836,19 @@ export const updateEvent = async ( // Let's try using `client.updateObject` directly which is more raw but allows data. // `targetObject.url` is what we need. - await client.updateObject({ + const updateResult = await client.updateObject({ url: targetObject.url, data: updatedIcalString, etag: targetObject.etag } as any); // Cast to any to bypass type definition mismatch + // Log result for debugging + if (updateResult && (updateResult as any).status && (updateResult as any).status >= 400) { + console.error('[APPLE CALENDAR] Update failed with status:', (updateResult as any).status); + throw new Error(`CalDAV update failed with status ${(updateResult as any).status}`); + } + console.log('[APPLE CALENDAR] Event updated successfully'); + return { id: eventId, title: event.summary, diff --git a/src/lib/calendar-events.ts b/src/lib/calendar-events.ts index e01a443..77079d2 100644 --- a/src/lib/calendar-events.ts +++ b/src/lib/calendar-events.ts @@ -956,7 +956,7 @@ export const createCalendarEvent = async ( const createdEvent = await import('./apple-calendar').then(m => m.createEvent(email, appPassword, calendarId, { title: event.title!, - description: event.description, + description: stripHtmlForCalDav(event.description), location: event.location, url: event.url, recurrence: event.recurrence, @@ -997,7 +997,7 @@ export const createCalendarEvent = async ( const createdEvent = await import('./synology-calendar').then(m => m.createEvent(serverUrl, username, password, calendarId, { title: event.title!, - description: event.description, + description: stripHtmlForCalDav(event.description), location: event.location, url: event.url, recurrence: event.recurrence, @@ -1066,6 +1066,11 @@ export const createCalendarEvent = async ( /** * Update an existing calendar event */ +const stripHtmlForCalDav = (html: string | undefined): string | undefined => { + if (!html) return html; + return html.replace(/<[^>]*>/g, '').trim(); +}; + export const updateCalendarEvent = async ( connection: CalendarConnection, calendarId: string, @@ -1186,7 +1191,7 @@ export const updateCalendarEvent = async ( const updatedEvent = await import('./apple-calendar').then(m => m.updateEvent(email, appPassword, calendarId, eventId, { title: event.title, - description: event.description, + description: stripHtmlForCalDav(event.description), location: event.location, url: event.url, start: event.start, @@ -1220,7 +1225,7 @@ export const updateCalendarEvent = async ( const updatedEvent = await import('./synology-calendar').then(m => m.updateEvent(serverUrl, username, password, calendarId, eventId, { title: event.title, - description: event.description, + description: stripHtmlForCalDav(event.description), location: event.location, url: event.url, start: event.start, diff --git a/src/lib/quotes.ts b/src/lib/quotes.ts index e5ab37b..a69ea98 100644 --- a/src/lib/quotes.ts +++ b/src/lib/quotes.ts @@ -55,7 +55,7 @@ export const LOCAL_QUOTES: LocalQuote[] = [ tags: ["life", "wisdom"], }, { - text: "Man muss das Unmogliche versuchen, um das Mogliche zu erreichen.", + text: "Man muss das Unmögliche versuchen, um das Mögliche zu erreichen.", author: "Hermann Hesse", language: "de", tags: ["motivation", "perseverance"], @@ -67,13 +67,13 @@ export const LOCAL_QUOTES: LocalQuote[] = [ tags: ["wisdom", "perseverance", "life"], }, { - text: "Es hort doch jeder nur, was er versteht.", + text: "Es hört doch jeder nur, was er versteht.", author: "Johann Wolfgang von Goethe", language: "de", tags: ["wisdom", "life"], }, { - text: "Ohne Musik ware das Leben ein Irrtum.", + text: "Ohne Musik wäre das Leben ein Irrtum.", author: "Friedrich Nietzsche", language: "de", tags: ["life", "creativity"], @@ -91,7 +91,7 @@ export const LOCAL_QUOTES: LocalQuote[] = [ tags: ["wisdom", "life", "motivation"], }, { - text: "Wer kampft, kann verlieren. Wer nicht kampft, hat schon verloren.", + text: "Wer kämpft, kann verlieren. Wer nicht kämpft, hat schon verloren.", author: "Bertolt Brecht", language: "de", tags: ["motivation", "perseverance", "success"], @@ -103,7 +103,7 @@ export const LOCAL_QUOTES: LocalQuote[] = [ tags: ["wisdom", "creativity"], }, { - text: "Was mich nicht umbringt, macht mich starker.", + text: "Was mich nicht umbringt, macht mich stärker.", author: "Friedrich Nietzsche", language: "de", tags: ["perseverance", "motivation"], @@ -127,7 +127,7 @@ export const LOCAL_QUOTES: LocalQuote[] = [ tags: ["wisdom", "motivation", "work"], }, { - text: "Auch aus Steinen, die einem in den Weg gelegt werden, kann man Schones bauen.", + text: "Auch aus Steinen, die einem in den Weg gelegt werden, kann man Schönes bauen.", author: "Johann Wolfgang von Goethe", language: "de", tags: ["perseverance", "creativity", "life"], @@ -151,13 +151,13 @@ export const LOCAL_QUOTES: LocalQuote[] = [ tags: ["creativity", "life", "humor"], }, { - text: "Lache nie uber die Dummheit der anderen. Sie ist deine Chance.", + text: "Lache nie über die Dummheit der anderen. Sie ist deine Chance.", author: "Winston Churchill", language: "de", tags: ["humor", "success", "wisdom"], }, { - text: "Leben ist das, was passiert, wahrend du eifrig dabei bist, andere Plane zu machen.", + text: "Leben ist das, was passiert, während du eifrig dabei bist, andere Pläne zu machen.", author: "John Lennon", language: "de", tags: ["life", "humor", "wisdom"], @@ -345,7 +345,7 @@ export const LOCAL_QUOTES: LocalQuote[] = [ }, // --- Additional German Quotes --- { - text: "Das Geheimnis des Vorwartskommens besteht darin, den ersten Schritt zu tun.", + text: "Das Geheimnis des Vorwärtskommens besteht darin, den ersten Schritt zu tun.", author: "Mark Twain", language: "de", tags: ["motivation", "work"], @@ -363,13 +363,13 @@ export const LOCAL_QUOTES: LocalQuote[] = [ tags: ["motivation", "work", "success"], }, { - text: "Die Zukunft gehort denen, die an die Schonheit ihrer Traume glauben.", + text: "Die Zukunft gehört denen, die an die Schönheit ihrer Träume glauben.", author: "Eleanor Roosevelt", language: "de", tags: ["motivation", "life", "creativity"], }, { - text: "Sei du selbst die Veranderung, die du dir wunschst fur diese Welt.", + text: "Sei du selbst die Veränderung, die du dir wünschst für diese Welt.", author: "Mahatma Gandhi", language: "de", tags: ["wisdom", "life", "motivation"], @@ -381,7 +381,7 @@ export const LOCAL_QUOTES: LocalQuote[] = [ tags: ["motivation", "life"], }, { - text: "Wer aufhort besser zu werden, hat aufgehort gut zu sein.", + text: "Wer aufhört besser zu werden, hat aufgehört gut zu sein.", author: "Philip Rosenthal", language: "de", tags: ["perseverance", "work", "success"], @@ -393,13 +393,13 @@ export const LOCAL_QUOTES: LocalQuote[] = [ tags: ["humor", "creativity", "wisdom"], }, { - text: "In der Mitte von Schwierigkeiten liegen die Moglichkeiten.", + text: "In der Mitte von Schwierigkeiten liegen die Möglichkeiten.", author: "Albert Einstein", language: "de", tags: ["perseverance", "motivation"], }, { - text: "Es gibt keine Abkurzung zu einem Ort, der es wert ist, erreicht zu werden.", + text: "Es gibt keine Abkürzung zu einem Ort, der es wert ist, erreicht zu werden.", author: "Beverly Sills", language: "de", tags: ["perseverance", "success", "work"], @@ -411,7 +411,7 @@ export const LOCAL_QUOTES: LocalQuote[] = [ tags: ["wisdom", "life"], }, { - text: "Kreativitat ist Intelligenz, die Spass hat.", + text: "Kreativität ist Intelligenz, die Spaß hat.", author: "Albert Einstein", language: "de", tags: ["creativity", "humor"], diff --git a/src/lib/synology-calendar.ts b/src/lib/synology-calendar.ts index e69279f..5aabc72 100644 --- a/src/lib/synology-calendar.ts +++ b/src/lib/synology-calendar.ts @@ -725,11 +725,12 @@ export const updateEvent = async ( hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false, }).formatToParts(d); const get = (t: string) => parts.find(p => p.type === t)?.value || '00'; + const tz = ICAL.Timezone.fromData({ tzid, component: comp.getFirstSubcomponent('vtimezone') || undefined }); return ICAL.Time.fromData({ year: parseInt(get('year')), month: parseInt(get('month')), day: parseInt(get('day')), hour: parseInt(get('hour')), minute: parseInt(get('minute')), second: parseInt(get('second')), isDate: false, - }); + }, tz); }; if (eventData.start) { @@ -739,6 +740,8 @@ export const updateEvent = async ( } else if (eventData.start.dateTime) { if (existingTzid) { event.startDate = toLocalIcalTime(eventData.start.dateTime, existingTzid); + const dtStartProp = vevent.getFirstProperty('dtstart'); + if (dtStartProp) dtStartProp.setParameter('tzid', existingTzid); } else { event.startDate = ICAL.Time.fromJSDate(new Date(eventData.start.dateTime), true); event.startDate.isDate = false; @@ -753,6 +756,8 @@ export const updateEvent = async ( } else if (eventData.end.dateTime) { if (existingTzid) { event.endDate = toLocalIcalTime(eventData.end.dateTime, existingTzid); + const dtEndProp = vevent.getFirstProperty('dtend'); + if (dtEndProp) dtEndProp.setParameter('tzid', existingTzid); } else { event.endDate = ICAL.Time.fromJSDate(new Date(eventData.end.dateTime), true); event.endDate.isDate = false;