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;