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-slot": "^1.1.1",
"@radix-ui/react-tabs": "^1.1.3", "@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-tooltip": "^1.1.6", "@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", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"dompurify": "^3.2.0",
"imapflow": "^1.0.0",
"lucide-react": "^0.469.0", "lucide-react": "^0.469.0",
"mailparser": "^3.7.0",
"next": "15.1.8", "next": "15.1.8",
"next-auth": "5.0.0-beta.30", "next-auth": "5.0.0-beta.30",
"nodemailer": "^7.0.0",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
"tailwind-merge": "^2.6.0", "tailwind-merge": "^2.6.0",
"tailwindcss-animate": "^1.0.7", "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", "tsdav": "^2.0.0",
"@schedule-x/react": "^2.0.0", "zod": "^3.24.1"
"@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"
}, },
"devDependencies": { "devDependencies": {
"@types/dompurify": "^3.2.0", "@types/dompurify": "^3.2.0",
"@types/mailparser": "^3.4.6",
"@types/node": "^22.10.5", "@types/node": "^22.10.5",
"@types/nodemailer": "^6.4.17", "@types/nodemailer": "^6.4.17",
"@types/react": "^19.0.4", "@types/react": "^19.0.4",

View File

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

View File

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

View File

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