From d9ab9934c0d776683dd15b9ae1917e622ebf3f36 Mon Sep 17 00:00:00 2001 From: theorose49 Date: Tue, 30 Jun 2026 15:50:11 +0900 Subject: [PATCH] =?UTF-8?q?feat(mail):=20=EB=B3=B8=EB=AC=B8=20HTML=20?= =?UTF-8?q?=EC=9A=B0=EC=84=A0(=EB=A9=94=EC=9D=BC=EC=95=B1=EC=B2=98?= =?UTF-8?q?=EB=9F=BC)=20+=20=EC=8A=A4=EB=A0=88=EB=93=9C=20=EC=88=9C?= =?UTF-8?q?=EB=B2=88(threadIndex/Count)=20=EC=A0=9C=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - GetFull: text/plain 대신 text/html 우선 → 서식 있는 본문 렌더 - handleListProjectMails: 스레드별 ts 정렬 순번 계산해 threadIndex(1=원문)/threadCount 반환 Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/httpapi/handlers_projects.go | 24 +++++++++++++++++++++++- internal/mailsync/mailsync.go | 7 ++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/internal/httpapi/handlers_projects.go b/internal/httpapi/handlers_projects.go index f0bb186..5c93cbf 100644 --- a/internal/httpapi/handlers_projects.go +++ b/internal/httpapi/handlers_projects.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "net/url" + "sort" "strings" "time" @@ -569,6 +570,8 @@ type mailItem struct { models.ProjectMailMsg Note string `json:"note"` // 공동 메모 본문 NoteEditedBy string `json:"noteEditedBy"` // 메모 마지막 수정자 + ThreadIndex int `json:"threadIndex"` // 스레드 내 순번(1=원문) + ThreadCount int `json:"threadCount"` // 스레드 총 메일 수 } // handleListProjectMails returns the stored client-domain mail for the project, @@ -623,6 +626,25 @@ func (s *Server) handleListProjectMails(w http.ResponseWriter, r *http.Request) notes[n.MessageID] = n } me := s.email(r) + // 스레드 내 순번 계산(저장된 전체 메일 기준, ts 오름차순). 1=원문. + type tpos struct{ idx, cnt int } + byThread := map[string][]models.ProjectMailMsg{} + for _, row := range rows { + byThread[row.ThreadID] = append(byThread[row.ThreadID], row) + } + pos := map[string]tpos{} + for tid, group := range byThread { + if tid == "" { + for _, g := range group { + pos[g.MessageID] = tpos{1, 1} + } + continue + } + sort.Slice(group, func(i, j int) bool { return group[i].TS < group[j].TS }) + for i, g := range group { + pos[g.MessageID] = tpos{i + 1, len(group)} + } + } // 가시성: 단일 메일 기준 — 그 메일의 from/to/cc에 내가 있으면 보인다(답장도 각각 개별 판정). items := make([]mailItem, 0, len(rows)) for _, row := range rows { @@ -630,7 +652,7 @@ func (s *Server) handleListProjectMails(w http.ResponseWriter, r *http.Request) if !m.Involves(me) { continue } - it := mailItem{ProjectMailMsg: row} + it := mailItem{ProjectMailMsg: row, ThreadIndex: pos[row.MessageID].idx, ThreadCount: pos[row.MessageID].cnt} if n, ok := notes[row.MessageID]; ok { it.Note = n.Body it.NoteEditedBy = n.LastEditedBy diff --git a/internal/mailsync/mailsync.go b/internal/mailsync/mailsync.go index f3a7d93..df857b2 100644 --- a/internal/mailsync/mailsync.go +++ b/internal/mailsync/mailsync.go @@ -364,11 +364,12 @@ func (s *Service) GetFull(ctx context.Context, mailbox, rfc822 string) (FullMess } walk(out.Payload) fm := FullMessage{GmailID: gid, Attachments: atts} - if plain != "" { - fm.Body = plain - } else { + // 메일앱처럼 보이도록 HTML 본문 우선, 없으면 텍스트. + if html != "" { fm.Body = html fm.IsHTML = true + } else { + fm.Body = plain } return fm, nil }