fix: TypeScript errors in IMAP, CalDAV, and calendar components

- Add @types/mailparser for proper type support
- Fix ImapFlow type narrowing for fetchOne, search, mailbox
- Add null checks for envelope in fetch loops
- Fix ListResponse cast to use double-cast pattern
- Fix CalendarView EventId type (string vs number)
- Fix CalDAV displayName type handling

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt 2026-02-07 08:18:05 +01:00
parent 615a36eb20
commit 076d6bc4d4
4 changed files with 32 additions and 25 deletions

View File

@ -20,31 +20,32 @@
"@radix-ui/react-slot": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-tooltip": "^1.1.6",
"@schedule-x/calendar": "^2.0.0",
"@schedule-x/drag-and-drop": "^2.0.0",
"@schedule-x/events-service": "^2.0.0",
"@schedule-x/react": "^2.0.0",
"@schedule-x/theme-default": "^2.0.0",
"@tiptap/react": "^2.0.0",
"@tiptap/starter-kit": "^2.0.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"dompurify": "^3.2.0",
"imapflow": "^1.0.0",
"lucide-react": "^0.469.0",
"mailparser": "^3.7.0",
"next": "15.1.8",
"next-auth": "5.0.0-beta.30",
"nodemailer": "^7.0.0",
"react": "19.0.0",
"react-dom": "19.0.0",
"tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7",
"zod": "^3.24.1",
"imapflow": "^1.0.0",
"nodemailer": "^7.0.0",
"mailparser": "^3.7.0",
"tsdav": "^2.0.0",
"@schedule-x/react": "^2.0.0",
"@schedule-x/calendar": "^2.0.0",
"@schedule-x/events-service": "^2.0.0",
"@schedule-x/drag-and-drop": "^2.0.0",
"@schedule-x/theme-default": "^2.0.0",
"@tiptap/react": "^2.0.0",
"@tiptap/starter-kit": "^2.0.0",
"dompurify": "^3.2.0"
"zod": "^3.24.1"
},
"devDependencies": {
"@types/dompurify": "^3.2.0",
"@types/mailparser": "^3.4.6",
"@types/node": "^22.10.5",
"@types/nodemailer": "^6.4.17",
"@types/react": "^19.0.4",

View File

@ -134,13 +134,13 @@ export function CalendarView({
try {
const currentEvents = eventsPlugin.getAll()
const currentIds = new Set(currentEvents.map((e: { id: string }) => e.id))
const newIds = new Set(scheduleEvents.map((e) => e.id))
const currentIds = new Set(currentEvents.map((e) => String(e.id)))
const newIds = new Set(scheduleEvents.map((e) => String(e.id)))
// Remove events no longer present
currentEvents.forEach((e: { id: string }) => {
if (!newIds.has(e.id)) {
eventsPlugin.remove(e.id)
currentEvents.forEach((e) => {
if (!newIds.has(String(e.id))) {
eventsPlugin.remove(String(e.id))
}
})

View File

@ -171,7 +171,7 @@ export async function getCalendars(
return calendars.map((cal: DAVCalendar) => ({
url: cal.url,
displayName: cal.displayName || 'Calendar',
displayName: (typeof cal.displayName === 'string' ? cal.displayName : null) || 'Calendar',
description: cal.description || undefined,
color:
(cal as Record<string, unknown>).calendarColor as string | undefined,

View File

@ -110,7 +110,7 @@ export async function listFolders(): Promise<MailFolder[]> {
path: mb.path,
name: parts[parts.length - 1] || mb.path,
delimiter: mb.delimiter || '/',
specialUse: (mb as Record<string, unknown>).specialUse as string | null ?? null,
specialUse: (mb as unknown as Record<string, unknown>).specialUse as string | null ?? null,
subscribed: mb.subscribed ?? false,
messages,
unseen,
@ -129,7 +129,8 @@ export async function listMessages(
return withClient(async (client) => {
const lock = await client.getMailboxLock(folder)
try {
const total = client.mailbox?.exists ?? 0
const mb = client.mailbox
const total = (mb && typeof mb === 'object' && 'exists' in mb) ? mb.exists ?? 0 : 0
if (total === 0) {
return { messages: [], total: 0 }
}
@ -151,6 +152,7 @@ export async function listMessages(
headers: ['content-type'],
})) {
const env = msg.envelope
if (!env) continue
messages.push({
uid: msg.uid,
from: env.from?.[0]
@ -184,8 +186,10 @@ export async function getMessage(
return withClient(async (client) => {
const lock = await client.getMailboxLock(folder)
try {
const raw = await client.fetchOne(String(uid), { source: true, flags: true, uid: true }, { uid: true })
if (!raw?.source) return null
const rawResult = await client.fetchOne(String(uid), { source: true, flags: true, uid: true }, { uid: true })
if (!rawResult) return null
const raw = rawResult
if (!raw.source) return null
const parsed = await simpleParser(raw.source)
@ -203,7 +207,7 @@ export async function getMessage(
flags: Array.from(raw.flags || []),
html: parsed.html || '',
text: parsed.text || '',
attachments: (parsed.attachments || []).map((att) => ({
attachments: (parsed.attachments || []).map((att: { filename?: string; contentType?: string; size?: number; contentId?: string; contentDisposition?: string }) => ({
filename: att.filename || 'attachment',
contentType: att.contentType || 'application/octet-stream',
size: att.size || 0,
@ -269,7 +273,7 @@ export async function deleteMessage(
// Try to move to Trash first
const mailboxes = await client.list()
const trash = mailboxes.find(
(mb) => (mb as Record<string, unknown>).specialUse === '\\Trash'
(mb) => (mb as unknown as Record<string, unknown>).specialUse === '\\Trash'
)
if (trash && folder !== trash.path) {
@ -292,7 +296,7 @@ export async function searchMessages(
return withClient(async (client) => {
const lock = await client.getMailboxLock(folder)
try {
const uids = await client.search(
const searchResult = await client.search(
{
or: [
{ subject: query },
@ -304,6 +308,7 @@ export async function searchMessages(
{ uid: true }
)
const uids = Array.isArray(searchResult) ? searchResult : []
if (uids.length === 0) return []
// Limit results
@ -317,6 +322,7 @@ export async function searchMessages(
flags: true,
}, { uid: true })) {
const env = msg.envelope
if (!env) continue
messages.push({
uid: msg.uid,
from: env.from?.[0]