All files / src utils.ts

100% Statements 37/37
92% Branches 23/25
100% Functions 11/11
100% Lines 31/31

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110      29x                                   2276x 125x       1042x       1215x                   327x 316x 316x 114x 114x 114x         203x   212x                 285x 283x     289x 289x 7x 7x   282x                       387x       387x 383x   389x 386x               274x 274x 274x 5x 5x         273x    
/** Shared utility functions for mail flag checks and provider config */
 
/** Well-known IMAP/SMTP server configurations for common email providers. */
export const WELL_KNOWN_PROVIDERS: Record<string, { imap_host: string; smtp_host: string }> = {
	"gmail.com": { imap_host: "imap.gmail.com", smtp_host: "smtp.gmail.com" },
	"googlemail.com": { imap_host: "imap.gmail.com", smtp_host: "smtp.gmail.com" },
	"outlook.com": { imap_host: "outlook.office365.com", smtp_host: "smtp.office365.com" },
	"hotmail.com": { imap_host: "outlook.office365.com", smtp_host: "smtp.office365.com" },
	"live.com": { imap_host: "outlook.office365.com", smtp_host: "smtp.office365.com" },
	"yahoo.com": { imap_host: "imap.mail.yahoo.com", smtp_host: "smtp.mail.yahoo.com" },
	"icloud.com": { imap_host: "imap.mail.me.com", smtp_host: "smtp.mail.me.com" },
	"me.com": { imap_host: "imap.mail.me.com", smtp_host: "smtp.mail.me.com" },
	"fastmail.com": { imap_host: "imap.fastmail.com", smtp_host: "smtp.fastmail.com" },
	"pm.me": { imap_host: "127.0.0.1", smtp_host: "127.0.0.1" },
	"protonmail.com": { imap_host: "127.0.0.1", smtp_host: "127.0.0.1" },
	"zoho.com": { imap_host: "imap.zoho.com", smtp_host: "smtp.zoho.com" },
};
 
/** Parse a comma-separated flags string into a Set for reliable lookup.
 *  Backend stores flags as comma-separated (e.g. "\\Seen,\\Flagged"). */
export function parseFlags(flags: string | null): Set<string> {
	if (!flags) return new Set();
	return new Set(flags.split(",").filter(Boolean));
}
 
export function isUnread(flags: string | null): boolean {
	return !parseFlags(flags).has("\\Seen");
}
 
export function isFlagged(flags: string | null): boolean {
	return parseFlags(flags).has("\\Flagged");
}
 
/**
 * Parse an address field into individual address strings.
 * Handles two storage formats:
 *  - JSON array: `["alice@test.com","bob@test.com"]` (from IMAP sync)
 *  - Comma-separated: `alice@test.com, bob@test.com` (from send route, demo data)
 */
export function parseAddressField(raw: string | null): string[] {
	if (!raw) return [];
	const trimmed = raw.trim();
	if (trimmed.startsWith("[")) {
		try {
			const parsed = JSON.parse(trimmed);
			if (Array.isArray(parsed)) return parsed.filter(Boolean);
		} catch {
			// Not valid JSON — fall through to comma-split
		}
	}
	return trimmed
		.split(",")
		.map((s) => s.trim())
		.filter(Boolean);
}
 
/**
 * Format an address field for display. Handles both JSON array and comma-separated
 * storage formats. Extracts display names from "Name <email>" format.
 */
export function formatAddressList(raw: string | null): string {
	if (!raw) return "";
	return parseAddressField(raw)
		.map((addr) => {
			// Match "Display Name <email>" or bare "<email>" pattern
			const match = addr.match(/^(.*?)\s*<([^>]+)>$/);
			if (match) {
				const name = (match[1] ?? "").replace(/^["']|["']$/g, "").trim();
				return name || match[2] || addr;
			}
			return addr;
		})
		.filter(Boolean)
		.join(", ");
}
 
/**
 * Extract plain text search terms from a query string, stripping field
 * operators (from:, to:, subject:, has:, is:, before:, after:, label:).
 * Returns regex-escaped terms with length > 1.
 */
export function extractSearchTerms(query: string): string[] {
	const textOnly = query
		.replace(/\b(from|to|subject|has|is|before|after|label):((?:"[^"]*")|(?:\S+))/gi, "")
		.replace(/\s+/g, " ")
		.trim();
	if (!textOnly) return [];
	return textOnly
		.split(/\s+/)
		.filter((t) => t.length > 1)
		.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
}
 
/**
 * Read messages-per-page preference from localStorage.
 * Returns the stored value or the default (50).
 */
export function getPageSize(): number {
	try {
		const stored = localStorage.getItem("stork-messages-per-page");
		if (stored) {
			const n = Number(stored);
			if (n > 0 && n <= 200) return n;
		}
	} catch {
		// localStorage unavailable
	}
	return 50;
}