diff --git a/package.json b/package.json index 721c71f..9105703 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.97.2", + "version": "1.97.3", "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/api/user/export-week-view/route.ts b/src/app/api/user/export-week-view/route.ts index 45107f5..b0fa81d 100644 --- a/src/app/api/user/export-week-view/route.ts +++ b/src/app/api/user/export-week-view/route.ts @@ -114,6 +114,22 @@ function dayDateLabel(d: Date, de: boolean): string { }); } +// Strip Apple Calendar icon encodings (=h=g, =i=g, etc.) and emoji from +// event titles before PDF rendering. Helvetica cannot render these glyphs and +// they appear as raw escape sequences. The colored dot/chip already identifies +// the calendar visually, so the icon character carries no extra information. +function pdfTitle(s: string | null | undefined, maxLen = 60): string { + if (!s) return ''; + return s + .replace(/(?:=[a-zA-Z])+/g, '') // Apple CalDAV icon escapes: =h=g, =i=g, =k=g … + .replace(/[\u{1F000}-\u{1FFFF}]/gu, '') // emoji (supplementary plane) + .replace(/[\u{2600}-\u{27BF}]/gu, '') // misc symbols & dingbats + .replace(/[\uE000-\uF8FF]/g, '') // Apple private-use area + .replace(/[\uFE00-\uFE0F\u200D]/g, '') // variation selectors / ZWJ + .trim() + .slice(0, maxLen); +} + function fmtHour(h: number): string { return `${String(h).padStart(2, '0')}:00`; } @@ -454,7 +470,7 @@ function WeekCalendarPDF({ return React.createElement(View, { key: t.id, style: { borderRadius: 2, paddingVertical: 1.5, paddingHorizontal: 4, marginBottom: 2, backgroundColor: DONE_BG }, - }, React.createElement(Text, { style: { fontSize: 7.5, color: DONE_TEXT } }, (t.title || '').slice(0, 36))); + }, React.createElement(Text, { style: { fontSize: 7.5, color: DONE_TEXT } }, pdfTitle(t.title, 36))); } if (t.isExternal) { // Calendar event: solid color chip with white text + dot indicator (matches webapp style) @@ -473,7 +489,7 @@ function WeekCalendarPDF({ React.createElement(View, { style: { width: 5, height: 5, borderRadius: 2.5, backgroundColor: evTextColor, opacity: 0.7, marginRight: 3, flexShrink: 0 }, }), - React.createElement(Text, { style: { fontFamily: 'Helvetica-Bold', fontSize: 7.5, color: evTextColor, flex: 1 } }, (t.title || '').slice(0, 36)), + React.createElement(Text, { style: { fontFamily: 'Helvetica-Bold', fontSize: 7.5, color: evTextColor, flex: 1 } }, pdfTitle(t.title, 36)), ); } // User-created all-day task: left border only, no background fill @@ -481,7 +497,7 @@ function WeekCalendarPDF({ return React.createElement(View, { key: t.id, style: { borderLeftWidth: 2, borderLeftColor: accent, paddingVertical: 1.5, paddingHorizontal: 4, marginBottom: 2 }, - }, React.createElement(Text, { style: { fontSize: 7.5, color: '#374151' } }, (t.title || '').slice(0, 36))); + }, React.createElement(Text, { style: { fontSize: 7.5, color: '#374151' } }, pdfTitle(t.title, 36))); }), allDayTasks.length > 5 ? React.createElement(Text, { style: { fontSize: 6, color: '#3b82f6', marginTop: 1 }, @@ -575,7 +591,7 @@ function WeekCalendarPDF({ key: t.id, style: { marginBottom: 1, paddingLeft: 1, paddingVertical: 1 }, }, - React.createElement(Text, { style: { fontSize: 7.5, color: DONE_TEXT, lineHeight: 1.3, textDecoration: 'line-through' } }, (t.title || '').slice(0, 60)), + React.createElement(Text, { style: { fontSize: 7.5, color: DONE_TEXT, lineHeight: 1.3, textDecoration: 'line-through' } }, pdfTitle(t.title)), ); } @@ -589,7 +605,7 @@ function WeekCalendarPDF({ paddingLeft: 3, paddingVertical: 1, marginBottom: 1, }, }, - React.createElement(Text, { style: { fontFamily: 'Helvetica-Bold', fontSize: 7.5, color: '#1e293b', lineHeight: 1.3 } }, (t.title || '').slice(0, 60)), + React.createElement(Text, { style: { fontFamily: 'Helvetica-Bold', fontSize: 7.5, color: '#1e293b', lineHeight: 1.3 } }, pdfTitle(t.title)), ); } @@ -601,7 +617,7 @@ function WeekCalendarPDF({ paddingVertical: 1, marginBottom: 1, }, }, - React.createElement(Text, { style: { fontSize: 7.5, color: '#1e293b', lineHeight: 1.3 } }, (t.title || '').slice(0, 60)), + React.createElement(Text, { style: { fontSize: 7.5, color: '#1e293b', lineHeight: 1.3 } }, pdfTitle(t.title)), ); }), ); @@ -719,7 +735,7 @@ function WeekCalendarPDF({ }), React.createElement(Text, { style: { fontSize: 7, color: '#374151', lineHeight: 1.3, flex: 1 }, - }, (t.title || '').slice(0, 52)), + }, pdfTitle(t.title)), ) ), list.tasks.length > 18 ? React.createElement(Text, {