diff options
| author | Bobby <[email protected]> | 2025-12-24 13:50:07 +0530 |
|---|---|---|
| committer | Bobby <[email protected]> | 2025-12-24 13:50:07 +0530 |
| commit | b77d75f05fb2059389c05f6c01484e0cd12e796e (patch) | |
| tree | e3c5521bf9ed3fcffd59960053d651091496a7ea /utils | |
| parent | 81ab367f440d6f85297b2013d0c1aa57fda7e9cd (diff) | |
| download | lain-b77d75f05fb2059389c05f6c01484e0cd12e796e.tar.xz lain-b77d75f05fb2059389c05f6c01484e0cd12e796e.zip | |
feat: introduce email folder synchronization and management, refactor email services, and update UI styles
Diffstat (limited to 'utils')
| -rw-r--r-- | utils/email/folders.go | 132 | ||||
| -rw-r--r-- | utils/format/date.go | 99 | ||||
| -rw-r--r-- | utils/format/html.go | 73 |
3 files changed, 304 insertions, 0 deletions
diff --git a/utils/email/folders.go b/utils/email/folders.go index 3acffae..1ae2bf4 100644 --- a/utils/email/folders.go +++ b/utils/email/folders.go @@ -1,11 +1,32 @@ package email import ( + "lain/models" "lain/types" + "net/url" + "strings" "github.com/emersion/go-imap" + "github.com/gofiber/fiber/v2" ) +func GetDisplayName(imapName string) string { + if strings.Contains(imapName, "/") { + parts := strings.Split(imapName, "/") + lastPart := parts[len(parts)-1] + if strings.ToLower(lastPart) == "inbox" { + return "Inbox" + } + return lastPart + } + + if strings.ToLower(imapName) == "inbox" { + return "Inbox" + } + + return imapName +} + func FetchFolders(client *types.EmailClient) ([]types.IMAPFolder, error) { mailboxes := make(chan *imap.MailboxInfo, 10) done := make(chan error, 1) @@ -28,3 +49,114 @@ func FetchFolders(client *types.EmailClient) ([]types.IMAPFolder, error) { return folders, nil } + +func GetFolderType(folderName string, iconMap map[string]types.FolderIconVariant) string { + nameLower := strings.ToLower(folderName) + + if strings.Contains(folderName, "/") { + parts := strings.Split(folderName, "/") + nameLower = strings.ToLower(parts[len(parts)-1]) + } + + for iconType := range iconMap { + if iconType == "default" { + continue + } + if strings.Contains(nameLower, iconType) { + return iconType + } + } + + return "default" +} + +func SortFolders(folders []models.Folder) { + for i := 0; i < len(folders)-1; i++ { + for j := 0; j < len(folders)-i-1; j++ { + if folders[j].SortOrder > folders[j+1].SortOrder { + folders[j], folders[j+1] = folders[j+1], folders[j] + } else if folders[j].SortOrder == folders[j+1].SortOrder { + if strings.ToLower(folders[j].IMAPName) > strings.ToLower(folders[j+1].IMAPName) { + folders[j], folders[j+1] = folders[j+1], folders[j] + } + } + } + } +} + +func GetSortOrder(folderName string, index int) int { + nameLower := strings.ToLower(folderName) + + if nameLower == "inbox" { + return 0 + } + if strings.Contains(nameLower, "draft") { + return 1 + } + if strings.Contains(nameLower, "sent") { + return 2 + } + if strings.Contains(nameLower, "archive") { + return 3 + } + if strings.Contains(nameLower, "trash") || strings.Contains(nameLower, "deleted") { + return 4 + } + if strings.Contains(nameLower, "spam") || strings.Contains(nameLower, "junk") { + return 5 + } + + if strings.Contains(folderName, "/") { + parts := strings.Split(folderName, "/") + baseOrder := GetSortOrder(parts[0], index) + return baseOrder + 1000 + (index * 10) + } + + return 100 + index +} + +func CopyFolderMap(folder fiber.Map) fiber.Map { + copy := fiber.Map{} + for k, v := range folder { + if k == "Subfolders" { + if subfolders, ok := v.([]fiber.Map); ok { + subfoldersCopy := make([]fiber.Map, len(subfolders)) + for i, sf := range subfolders { + subfoldersCopy[i] = CopyFolderMap(sf) + } + copy[k] = subfoldersCopy + } + } else { + copy[k] = v + } + } + return copy +} + +func IsVirtualFolder(folderName string) bool { + return strings.HasPrefix(folderName, "Virtual") || strings.Contains(folderName, "/Virtual") +} + +func UpdateActiveFolder(folders []fiber.Map, activeFolder string) []fiber.Map { + decodedActive, _ := url.QueryUnescape(activeFolder) + activeLower := strings.ToLower(decodedActive) + + foldersCopy := make([]fiber.Map, len(folders)) + for i, f := range folders { + foldersCopy[i] = CopyFolderMap(f) + } + + var updateActive func([]fiber.Map) + updateActive = func(folderList []fiber.Map) { + for i := range folderList { + imapNameLower := strings.ToLower(folderList[i]["IMAPName"].(string)) + folderList[i]["Active"] = imapNameLower == activeLower + if subfolders, ok := folderList[i]["Subfolders"].([]fiber.Map); ok && len(subfolders) > 0 { + updateActive(subfolders) + } + } + } + + updateActive(foldersCopy) + return foldersCopy +} diff --git a/utils/format/date.go b/utils/format/date.go new file mode 100644 index 0000000..8ef95cb --- /dev/null +++ b/utils/format/date.go @@ -0,0 +1,99 @@ +package format + +import ( + "html" + "lain/types" + "time" +) + +func FormatEmailDate(date time.Time, dateFormat types.DateFormat, timeFormat types.TimeFormat, prettyDates bool, timezone string) string { + loc, err := time.LoadLocation(timezone) + if err != nil { + loc = time.UTC + } + + date = date.In(loc) + now := time.Now().In(loc) + + if prettyDates { + return formatPrettyDate(date, now, timeFormat) + } + + return formatFullDate(date, dateFormat, timeFormat) +} + +func formatPrettyDate(date, now time.Time, timeFormat types.TimeFormat) string { + diff := now.Sub(date) + + // Today - show time only + if date.Year() == now.Year() && date.YearDay() == now.YearDay() { + return formatTime(date, timeFormat) + } + + // Yesterday + yesterday := now.AddDate(0, 0, -1) + if date.Year() == yesterday.Year() && date.YearDay() == yesterday.YearDay() { + return "Yesterday" + } + + // This week - show day name and time + if diff.Hours() < 168 { // 7 days + dayName := date.Format("Mon") + timeStr := formatTime(date, timeFormat) + return dayName + " " + timeStr + } + + // This year - show month and day + if date.Year() == now.Year() { + return date.Format("Jan 2") + } + + // Older - show full date + return date.Format("Jan 2, 2006") +} + +func formatFullDate(date time.Time, dateFormat types.DateFormat, timeFormat types.TimeFormat) string { + dateStr := formatDate(date, dateFormat) + timeStr := formatTime(date, timeFormat) + return dateStr + " " + timeStr +} + +func formatDate(date time.Time, dateFormat types.DateFormat) string { + switch dateFormat { + case types.YearMonthDayDashed: + return date.Format("2006-01-02") + case types.YearMonthDaySlashed: + return date.Format("2006/01/02") + case types.YearMonthDayDotted: + return date.Format("2006.01.02") + case types.DayMonthYearDashed: + return date.Format("02-01-2006") + case types.DayMonthYearSlashed: + return date.Format("02/01/2006") + case types.DayMonthYearDotted: + return date.Format("02.01.2006") + case types.DayMonthYearDottedShort: + return date.Format("2.1.06") + default: + return date.Format("2006-01-02") + } +} + +func formatTime(date time.Time, timeFormat types.TimeFormat) string { + switch timeFormat { + case types.ShortHoursAndMinutes24Hours: + return date.Format("15:4") + case types.FullHoursAndMinutes24Hours: + return date.Format("15:04") + case types.ShortHoursAndMinutes12Hours: + return date.Format("3:4 PM") + case types.FullHoursAndMinutes12Hours: + return date.Format("03:04 PM") + default: + return date.Format("15:04") + } +} + +func DecodeHTML(text string) string { + return html.UnescapeString(text) +} diff --git a/utils/format/html.go b/utils/format/html.go new file mode 100644 index 0000000..36e2425 --- /dev/null +++ b/utils/format/html.go @@ -0,0 +1,73 @@ +package format + +import ( + "regexp" + "strings" +) + +func GenerateSnippet(bodyText, bodyHTML string) string { + text := bodyText + if text == "" && bodyHTML != "" { + text = StripHTML(bodyHTML) + } + + text = strings.TrimSpace(text) + if len(text) > 150 { + text = text[:150] + "..." + } + + return text +} + +func StripHTML(html string) string { + text := html + + styleRegex := regexp.MustCompile(`(?i)<style[^>]*>[\s\S]*?</style>`) + text = styleRegex.ReplaceAllString(text, "") + + scriptRegex := regexp.MustCompile(`(?i)<script[^>]*>[\s\S]*?</script>`) + text = scriptRegex.ReplaceAllString(text, "") + + headRegex := regexp.MustCompile(`(?i)<head[^>]*>[\s\S]*?</head>`) + text = headRegex.ReplaceAllString(text, "") + + text = strings.ReplaceAll(text, "<br>", "\n") + text = strings.ReplaceAll(text, "<br/>", "\n") + text = strings.ReplaceAll(text, "<br />", "\n") + text = strings.ReplaceAll(text, "</p>", "\n\n") + text = strings.ReplaceAll(text, "</div>", "\n") + text = strings.ReplaceAll(text, "</tr>", "\n") + text = strings.ReplaceAll(text, "</h1>", "\n") + text = strings.ReplaceAll(text, "</h2>", "\n") + text = strings.ReplaceAll(text, "</h3>", "\n") + text = strings.ReplaceAll(text, "</li>", "\n") + + inTag := false + var result strings.Builder + for _, char := range text { + if char == '<' { + inTag = true + continue + } + if char == '>' { + inTag = false + continue + } + if !inTag { + result.WriteRune(char) + } + } + + cleanText := result.String() + + lines := strings.Split(cleanText, "\n") + var cleanLines []string + for _, line := range lines { + line = strings.TrimSpace(line) + if line != "" { + cleanLines = append(cleanLines, line) + } + } + + return strings.TrimSpace(strings.Join(cleanLines, " ")) +} |
