fix(mail): 답장도 리스트에 각각 표시(스레드 단위 가시성) + 동기화 진행 신호
All checks were successful
build-and-push / build (push) Successful in 32s
All checks were successful
build-and-push / build (push) Successful in 32s
- 가시성을 메일 단위 → '내가 참여한 스레드 전체'로: 한 스레드에서 한 통이라도 from/to/cc면 그 스레드의 원문·답장 모두 노출(각각의 행). 무관 스레드는 숨김. - isSyncing(): 백필/수동 동기화 진행 중이면 mails 응답에 syncing=true → 프론트 폴링 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7e1bb6606e
commit
14e8a62f76
@ -603,6 +603,9 @@ func (s *Server) handleListProjectMails(w http.ResponseWriter, r *http.Request)
|
|||||||
if st.LastError != "" {
|
if st.LastError != "" {
|
||||||
resp["error"] = st.LastError
|
resp["error"] = st.LastError
|
||||||
}
|
}
|
||||||
|
if isSyncing(id) {
|
||||||
|
resp["syncing"] = true
|
||||||
|
}
|
||||||
// First ever view → kick off full backfill in the background (only if mail
|
// First ever view → kick off full backfill in the background (only if mail
|
||||||
// integration is configured). Already-stored mail is shown regardless.
|
// integration is configured). Already-stored mail is shown regardless.
|
||||||
if !synced && s.mail.Enabled() {
|
if !synced && s.mail.Enabled() {
|
||||||
@ -619,10 +622,24 @@ func (s *Server) handleListProjectMails(w http.ResponseWriter, r *http.Request)
|
|||||||
notes[n.MessageID] = n
|
notes[n.MessageID] = n
|
||||||
}
|
}
|
||||||
me := s.email(r)
|
me := s.email(r)
|
||||||
|
// 가시성: 내가 참여한 "스레드 전체"를 노출(원문 + 답장 각각). 한 스레드에서
|
||||||
|
// 한 통이라도 내가 from/to/cc면 그 스레드의 모든 메일이 보인다.
|
||||||
|
myThreads := map[string]bool{}
|
||||||
|
involvedSolo := map[string]bool{}
|
||||||
|
for _, row := range rows {
|
||||||
|
m := mailsync.Message{From: row.FromAddr, To: row.ToAddr, Cc: row.CcAddr}
|
||||||
|
if m.Involves(me) {
|
||||||
|
if row.ThreadID != "" {
|
||||||
|
myThreads[row.ThreadID] = true
|
||||||
|
} else {
|
||||||
|
involvedSolo[row.MessageID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
items := make([]mailItem, 0, len(rows))
|
items := make([]mailItem, 0, len(rows))
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
m := mailsync.Message{ID: row.MessageID, From: row.FromAddr, To: row.ToAddr, Cc: row.CcAddr}
|
visible := involvedSolo[row.MessageID] || (row.ThreadID != "" && myThreads[row.ThreadID])
|
||||||
if !m.Involves(me) {
|
if !visible {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
it := mailItem{ProjectMailMsg: row}
|
it := mailItem{ProjectMailMsg: row}
|
||||||
|
|||||||
@ -16,6 +16,12 @@ import (
|
|||||||
// syncInFlight prevents concurrent syncs of the same project.
|
// syncInFlight prevents concurrent syncs of the same project.
|
||||||
var syncInFlight sync.Map // projectID -> struct{}
|
var syncInFlight sync.Map // projectID -> struct{}
|
||||||
|
|
||||||
|
// isSyncing reports whether a sync is currently running for the project.
|
||||||
|
func isSyncing(projectID string) bool {
|
||||||
|
_, ok := syncInFlight.Load(projectID)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
// syncProjectMail pulls the project's client-domain mail across ALL active member
|
// syncProjectMail pulls the project's client-domain mail across ALL active member
|
||||||
// mailboxes (domain-wide delegation), upserts the headers into ProjectMailMsg, and
|
// mailboxes (domain-wide delegation), upserts the headers into ProjectMailMsg, and
|
||||||
// records sync state. full=true pages the entire history; otherwise just the newest
|
// records sync state. full=true pages the entire history; otherwise just the newest
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user