fix: strip Apple Calendar icon escapes and emoji from PDF event titles
Apple Calendar encodes calendar icons as =h=g / =i=g escape sequences in iCalendar SUMMARY fields during CalDAV sync. Helvetica cannot render the underlying glyphs, causing the raw escape bytes to appear verbatim in the PDF. Add pdfTitle() helper that strips these patterns along with emoji and Apple private-use area characters before rendering any title text. The calendar color chip/dot already provides the visual calendar identity. v1.97.3 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
ac17ca2635
commit
cf7e138bc9
@ -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": {
|
||||
|
||||
@ -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, {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user