diff --git a/docs/design/webmail/Compose.dc.html b/docs/design/webmail/Compose.dc.html index afcbd38..d8c477e 100644 --- a/docs/design/webmail/Compose.dc.html +++ b/docs/design/webmail/Compose.dc.html @@ -13,6 +13,34 @@ body{margin:0} a{color:#0E7C86}a:hover{color:#0A5F67} :focus-visible{outline:2px solid #0E7C86;outline-offset:2px} +/* 글자 입력칸 포커스: 커서가 있는 칸의 아래 선이 2px 브랜드색 (받는 사람 · 참조 · 숨은참조 · 제목 · 본문 공통) */ +.cf:has(.cf-in:focus){border-bottom-color:#0E7C86 !important;box-shadow:inset 0 -1px 0 #0E7C86} +.cf-in:focus,.cf-in:focus-visible{outline:none} +/* 본문 편집기 */ +.ce p{margin:0} +.ce ul,.ce ol{margin:0;padding-left:24px} +.ce:empty::before{content:attr(data-placeholder);float:left;height:0;color:#667085;pointer-events:none} +/* 툴바 버튼: 올림 · 켜짐 · 비활성 · 메뉴 열림 */ +.tb{background:transparent;color:#344054} +.tb:hover,.tb[aria-expanded="true"]{background:#F2F4F7;color:#171C26} +.tb[aria-pressed="true"]{background:#E3F2F3;color:#0A5F67;box-shadow:inset 0 0 0 1px #0E7C86} +.tb[aria-pressed="true"]:hover{background:#D5EBED;color:#0A5F67} +.tb:disabled,.tb:disabled:hover{opacity:.5;cursor:default;background:transparent;color:#344054} +.mi{background:transparent;color:#171C26} +.mi:hover{background:#F2F4F7} +.mi:disabled,.mi:disabled:hover{opacity:.5;cursor:default;background:transparent} +.sw{background:transparent} +.sw:hover{background:#F2F4F7} +/* 어두운 바탕(알림)에서는 강조색 고리가 묻혀 밝은 색으로 바꾼다 */ +.on-dark:focus-visible{outline-color:#9ED6DB} +/* 좁은 폭: 쓰기 칸 폭을 보고 묶음을 정해진 순서로 더보기에 옮긴다 (참고값 — 구현은 실제 폭을 잰다) */ +.cmp{container:cmp/inline-size} +.mm-a,.mm-b,.mm-d,.mm-e,.mm-f{display:none !important} +@container cmp (max-width:733px){.tg-a{display:none !important}.mm-a{display:flex !important}} +@container cmp (max-width:638px){.tg-b{display:none !important}.mm-b{display:flex !important}} +@container cmp (max-width:459px){.tg-d{display:none !important}.mm-d{display:flex !important}} +@container cmp (max-width:364px){.tg-f{display:none !important}.mm-f{display:flex !important}} +@container cmp (max-width:311px){.tg-e{display:none !important}.mm-e{display:flex !important}}
@@ -122,7 +150,7 @@ a{color:#0E7C86}a:hover{color:#0A5F67}
-
+

{{ title }}

@@ -131,7 +159,7 @@ a{color:#0E7C86}a:hover{color:#0A5F67}
-
+
@@ -142,7 +170,7 @@ a{color:#0E7C86}a:hover{color:#0A5F67} - +
@@ -155,30 +183,423 @@ a{color:#0E7C86}a:hover{color:#0A5F67} -
+
- +
-
+
- +
-
+
- +
- +
-
+
+ +
+ + + +
+ +
+
+ + +
+ + + +
+ + + +
+
+
+
+ + +
+ + + +
+
+
+ +
+ +
+ + + + +
+ +
+
+ + +
+
+ 글자 색 + {{ colorName }} +
+
+ + + +
+
+
+
+
+ + +
+
+ 배경 색 + {{ bgName }} +
+
+ + + +
+
+
+
+ +
+ +
+ + + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+ +
+ + +
+ +
+ + +
+
+ +
+ + +
+
+ +
+ +
+ + +
+
+
+ +
+
+ + +
+ + + +
+
+ +
+ + +
+
+ +
+
+ +
+ + + +
+
+ + +
+
+ +
+ 표 넣기 +
+ + + +
+ {{ tableSize }} +
+
+
+ +
+ +
+ + + + +
+

도윤님, 확인했습니다.

+

체크리스트 기준으로 금요일 저녁 전환을 진행해 주세요.

+
    +
  • 전환 하루 전에 DNS TTL을 300초로 낮추기
  • +
  • 기존 서버는 2주 동안 함께 운영
  • +
+


+

김아라

+
+
+

원본 메일

+ + + + + + +
보낸 사람이도윤 <doyun.lee@arasystem.it.kr>
날짜2026. 10. 26. (월) 오전 10:42
제목Re: 메일 서버 이전 일정 공유
+
+

김아라님, 안녕하세요.

+


+

말씀하신 대로 이번 주 금요일(10/30) 저녁 9시에 MX 레코드를 새 메일 서버로 전환하겠습니다.

+

전환 하루 전에 DNS TTL을 300초로 낮춰 두고, 기존 서버는 2주 동안 함께 운영합니다.

+


+

전환 체크리스트를 첨부했으니 확인 부탁드립니다.

+


+

이도윤 드림

+
+
+
+ +
+


+
+
+

원본 메일

+ + + + + + + + +
보낸 사람이도윤 <doyun.lee@arasystem.it.kr>
날짜2026. 10. 26. (월) 오전 10:42
받는 사람김아라 <ara.kim@arasystem.it.kr>
참조인프라팀 <infra@arasystem.it.kr>
제목Re: 메일 서버 이전 일정 공유
+
+

김아라님, 안녕하세요.

+


+

말씀하신 대로 이번 주 금요일(10/30) 저녁 9시에 MX 레코드를 새 메일 서버로 전환하겠습니다.

+

전환 하루 전에 DNS TTL을 300초로 낮춰 두고, 기존 서버는 2주 동안 함께 운영합니다.

+


+

전환 체크리스트를 첨부했으니 확인 부탁드립니다.

+


+

이도윤 드림

+
+
+
+ +
+
+
+ +
+ +
- 파일당 최대 20MB · 문서, 이미지, 압축 파일 + 파일당 최대 20MB · 합계 25MB · 최대 20개 · 문서, 이미지, 압축 파일
    @@ -208,33 +629,153 @@ a{color:#0E7C86}a:hover{color:#0A5F67}
+ + +
+ + 이미지는 본문 대신 첨부 파일로 올렸습니다. + +
+
- + + + + + + +
+ +
+

메일 쓰기 · 편집기 상태

+

본문이 서식 편집기로 바뀝니다. 글자를 쓰는 칸의 포커스는 한 가지 모양으로 보이고, 툴바 버튼은 메일 툴바 버튼과 같은 44px 높이를 씁니다. 본문 글꼴은 맑은 고딕입니다.

+
+ +
+ +
+ +
+
+

글자 입력칸 포커스 — 한 가지 규칙

+

받는 사람 · 참조 · 숨은참조 · 제목 · 본문 편집기. 커서가 있는 칸의 아래 선이 2px 브랜드색이 됩니다.

+
+ +
+ +
+
+
+ 받는 사람 + + + 이도윤 <doyun.lee@arasystem.it.kr> + + + + + 참조 · 숨은참조 +
+
+ 제목 + Re: 메일 서버 이전 일정 공유 +
+
+
① 받는 사람 · 참조 · 숨은참조 — 칩까지 포함한 행 전체의 아래 선
+
+ +
+
+
+ 받는 사람 + + + 이도윤 <doyun.lee@arasystem.it.kr> + + + + 참조 · 숨은참조 +
+
+ 제목 + Re: 메일 서버 이전 일정 공유 +
+
+
② 제목 — 같은 자리, 같은 두께
+
+ +
+
+
+ + + + + + + + + + +
+
+

도윤님, 확인했습니다.

+

체크리스트 기준으로 금요일 저녁 전환을 진행해 주세요.

+
+
+ + 파일 첨부 +
+
+
③ 본문 편집기 — 편집 영역 아래 선. 툴바 아래 선은 그대로
+
+ +
+
+
+ 받는 사람 + + + 이도윤 <doyun.lee@arasystem.it.kr> + + + + +
+
+ 제목 + Re: 메일 서버 이전 일정 공유 +
+
+
④ 행 안의 버튼 — 버튼에만 고리. 행의 선은 회색 그대로
+
+
+ +
+ 구현 — tokens.css 변수 + .compose-field:has(:is(input, .ProseMirror):focus)
  border-bottom-color: var(--color-brand)
  box-shadow: inset 0 calc(var(--border-width) * -1) 0 var(--color-brand)
.compose-field :is(input, .ProseMirror):focus-visible → outline: none
+
+
+ +
+
+

규칙 — 옮길 값

+

버튼 포커스와 같은 색 · 같은 두께, 모양만 다릅니다

+
+
+
대상
+
받는 사람 · 참조 · 숨은참조 · 제목 · 본문 편집기
+
위치
+
그 칸의 아래 hairline 자리. 본문은 편집 영역 아래(첨부 영역 위)
+
두께 · 색
+
2px #0E7C86 — 1px 선 색을 바꾸고 안쪽 1px 를 더해 높이가 변하지 않는다
+
모서리
+
없음 (곧은 선)
+
범위
+
행 전체 폭 — 원래 hairline 과 같은 길이 (콘텐츠 열, 최대 800px)
+
언제
+
글자 입력 요소에 포커스가 있을 때. 마우스로 눌러도 보인다
+
대비
+
흰 바탕 4.9:1 · 원래 선 #EEF0F3 4.3:1 · 오류 띠 #FEF3F2 4.5:1
+
버튼
+
2px 고리, 바깥 2px (전역 규칙 그대로). 누르는 것은 고리, 쓰는 곳은 선 — 한 번에 하나만 보인다
+
상자 입력
+
테두리가 있는 입력칸(링크 팝오버 · 로그인)은 상자가 있으므로 버튼처럼 고리
+
+
+
+ +
+ +
+
+

본문 툴바 — 800px 열

+

버튼 40×44 (폭 40 은 밀도 예외 · 사용자 확정) · 아이콘 16 · 버튼 사이 2 · 묶음 사이 세로선 1×20 · 툴바 높이 52 (위아래 4)

+
+
+
+
+ + + +
+
+ + + +
+
+ + + + +
+
+ + + +
+
+ + + +
+
+ + +
+ +
+ + +
+
    +
  • 붉은 세로선이 콘텐츠 열 32px 선 — 첫 아이콘(실행 취소)이 여기서 시작한다 (툴바 왼쪽 여백 20 + 버튼 안 12).
  • +
  • 취소선 · 정렬 · 내어쓰기 · 들여쓰기 · 인용 · 표 · 가로줄 · 서식 지우기는 늘 더보기에 있다.
  • +
  • 이미지 버튼은 두지 않는다 — 본문에 넣은 이미지는 첨부 파일로 올린다 (맨 아래 카드).
  • +
  • 키보드: 툴바는 Tab 한 번에 들어가 화살표로 옮긴다. 본문에서 Alt+F10 → 툴바, Esc → 본문.
  • +
+
+ +
+
+

툴바 버튼 상태

+

MailToolButton 과 같은 높이 44 · 모서리 8 · 글자색

+
+
+
+ + 기본 + 바탕 없음 · 아이콘 #344054 +
+
+ + 마우스 올림 + 바탕 #F2F4F7 · 아이콘 #171C26 +
+
+ + 켜짐 (눌림) + 바탕 #E3F2F3 · 안쪽 1px #0E7C86 · 아이콘 #0A5F67 +
+
+ + 비활성 + 50% 투명 (MailToolButton 과 같다) +
+
+ + 포커스 + 2px #0E7C86 고리 · 바깥 2px +
+
+ + 메뉴 열림 + 올림과 같은 바탕 +
+
+

켜짐에 안쪽 1px 선을 두는 까닭 — 옅은 바탕(#E3F2F3)만으로는 흰 바탕과의 대비가 3:1 에 못 미친다. 선은 4.3:1.

+
+ 밀도 예외 — 사용자 확정 (2026-10-27) + 클릭 영역 44px 기준의 의도된 예외: 툴바 버튼 폭 40 (높이 44) · 메뉴 항목 높이 40 · 색 견본 칸 40×40 · 표 고르기 칸 22 (간격 3, 키보드는 화살표) +
+
+
+ +
+ +
+
+

좁은 폭 — 넘치는 묶음은 더보기로

+

툴바는 늘 한 줄입니다. 정해진 순서로 묶음째 더보기에 들어가고, 더보기 단추에는 숫자를 달지 않습니다.

+
+
+
+
+
쓰기 칸 600px
+
+
+ + + + + + + + + + + + + +
+
+
+
+
쓰기 칸 340px (휴대폰)
+
+
+ + + + + + + + +
+
+
+
    +
  1. 실행 취소 · 다시 실행 — 쓰기 칸 734px 미만 (Ctrl+Z · Ctrl+Y 는 그대로)
  2. +
  3. 글꼴 · 글자 크기 — 639px 미만
  4. +
  5. 글자 색 · 배경 색 — 460px 미만
  6. +
  7. 링크 — 365px 미만 (주소를 붙여 넣으면 저절로 링크가 된다)
  8. +
  9. 글머리 · 번호 목록 — 312px 미만
  10. +
+

굵게 · 기울임 · 밑줄과 더보기는 늘 남습니다. 폭은 지금 크기로 셈한 참고값이고, 구현은 툴바의 실제 폭을 재어(ResizeObserver) 이 순서로 넘깁니다.

+
+
+
340px 에서 연 더보기 (위쪽)
+
+ + +
+ + +
+ + + +
+

아래로 정렬 · 들여쓰기 · 링크 넣기 · 인용 · 표 · 가로줄 · 서식 지우기. 화면 높이에 맞춰 안에서 스크롤

+
+

값을 고르는 것(글꼴 · 크기 · 색)은 › 로 한 단계 더 엽니다.

+
+
+
+ +
+
+

빈 본문

+

새 메일처럼 본문이 비었을 때 (커서가 본문에 있는 상태)

+
+
+
+ + + + + + + + + + +
+
+

내용을 입력하세요

+
+
+ + 파일 첨부 +
+
+

안내 문구는 지금과 같은 "내용을 입력하세요" · #667085 (흰 바탕 4.9:1). 한 글자라도 쓰면 사라지고, 커서는 문구 앞에 선다.

+
+
+ +
+ +
+
+

더보기 메뉴

+

800px — 툴바에 없는 서식 · 항목 높이 40 (밀도 예외 · 사용자 확정)

+
+
+
+ + + +
+
+ +
+ + + +
+ + +
+ + + +
+ +
+
+

켜진 서식은 오른쪽 ✓ (취소선 · 정렬 · 인용). 단축키는 흐린 글자. 올린 항목은 #F2F4F7 (가운데 정렬).

+
+ +
+
+

글꼴 · 글자 크기

+

받는 쪽에 흔히 있는 글꼴만 — 이름은 그 글꼴로 보입니다

+
+
+
+ +
+ + + + +
+ + + + + +
+
+
+ +
+ + + + + + + + +
+
+
+

맑은 고딕이 본문 기본 글꼴이라 받는 쪽에서도 같은 모양입니다(맥에서는 Apple SD 산돌고딕 Neo). 크기는 px 숫자 · 기본 14 · 제목 스타일은 두지 않습니다. 고른 글자의 크기가 섞이면 "–".

+
+ +
+
+

글자 색 · 배경 색

+

8색씩 — 글자 색은 모두 흰 바탕 4.5:1 이상

+
+
+
+ +
+
글자 색빨강
+
+ + + + + + + + +
+
+
+
+ +
+
배경 색노랑
+
+ + + + + + + + +
+
+
+
+

견본 칸 40×40 (보이는 색 26 · 밀도 예외 · 사용자 확정). 고른 색에 ✓ 하고 이름을 위에 글자로도 씁니다. "기본"은 글자 색을, "없음"은 배경 색을 지웁니다.

+
+
+ +
+ +
+
+

답장 · 전달 — 편집기 안의 원문 인용

+

원문은 회색 인용 막대 안에 "원본 메일" 머리와 함께 둡니다(답장 · 전달 모두). 내 글은 그 위에 씁니다.

+
+
+
+
+

도윤님, 확인했습니다.

+

체크리스트 기준으로 금요일 저녁 전환을 진행해 주세요.

+

 

+

김아라

+
+
+

원본 메일

+ + + + + + +
보낸 사람이도윤 <doyun.lee@arasystem.it.kr>
날짜2026. 10. 26. (월) 오전 10:42
제목Re: 메일 서버 이전 일정 공유
+
+

김아라님, 안녕하세요.

+

 

+

말씀하신 대로 이번 주 금요일(10/30) 저녁 9시에 MX 레코드를 새 메일 서버로 전환하겠습니다.

+ +
+
+
답장 — 보낸 사람 · 날짜 · 제목. 커서는 맨 위 첫 줄
+
+
+
+

 

+
+
+

원본 메일

+ + + + + + + + +
보낸 사람이도윤 <doyun.lee@arasystem.it.kr>
날짜2026. 10. 26. (월) 오전 10:42
받는 사람김아라 <ara.kim@arasystem.it.kr>
참조인프라팀 <infra@arasystem.it.kr>
제목Re: 메일 서버 이전 일정 공유
+
+

김아라님, 안녕하세요.

+ +
+
+
전달 — 받는 사람 · 참조가 더해진다. 커서는 받는 사람 칸
+
+
+

인용 막대 2px #D0D5DD · 막대와 글 사이 14 · 원문 글 #4A5363 · 머리 12.5px(항목 이름 #667085) · 내 글과 24 띄움. 막대를 회색으로 둔 것은 2px 브랜드색 선(포커스)과 헷갈리지 않게 하려는 것. 툴바의 "인용"도 같은 막대를 머리 없이 쓴다.

+
+ +
+
+

링크

+

주소와 표시할 글자 — 이미 있는 링크를 고칠 때

+
+

진행 상황은 전환 안내 페이지에서 볼 수 있습니다.

+
+
+ + +
+
+ + +
+
+ + + + +
+
+

Enter = 적용 · Esc = 닫고 본문으로. 새 링크는 "링크 빼기" 없이 "넣기". 주소는 http(s) · mailto 만. 상자 입력칸이라 포커스는 고리(주소 칸).

+
+
+ +
+ +
+
+

본문에 넣은 이미지 → 첨부 파일

+

본문에는 이미지를 넣지 않습니다(사용자 확정). 붙여 넣거나 끌어다 놓은 이미지는 첨부 파일로 올리고, 툴바에는 이미지 버튼이 없습니다.

+
+
+
+
① 끌어다 놓는 중
+
+

화면 캡처 함께 보냅니다.

+ +
+
+
+
② 첨부 목록에 올라간다
+
+ 파일 첨부 + 파일당 최대 20MB · 합계 25MB · 최대 20개 · 문서, 이미지, 압축 파일 +
    +
  • +
    + + image.png + 올리는 중 62% + +
    +
    +
  • +
+
+
+
+
③ 이미지였으면 알린다
+
+ + 이미지는 본문 대신 첨부 파일로 올렸습니다. + +
+

성공 알림과 같은 모양 · 5초 뒤 닫힌다

+
+
+
    +
  • 붙여 넣기(Ctrl+V) · 끌어다 놓기 모두. 끌어다 놓은 다른 파일도 첨부로 올린다 — 알림은 이미지를 돌렸을 때만.
  • +
  • 한도는 첨부와 같다. 넘치면 첨부 목록에 실패로 보인다 (오류 · 빈 상태 보드의 "첨부 업로드").
  • +
  • 끌어다 놓는 동안의 덮개는 2px #0E7C86 점선 · 바탕 #F3F8F8 — 포커스 선(실선)과 모양이 다르다.
  • +
+
+ +
+
+

표 넣기

+

더보기 › 표 넣기 — 행 · 열을 고릅니다

+
+
+ 표 넣기 + + 3행 × 4열 +
+

칸 22px · 간격 3 (밀도 예외 · 사용자 확정). 마우스는 칸 위로 움직여 고르고 누르면 넣습니다. 키보드는 화살표 + Enter. 표 안의 행 · 열 추가와 삭제는 다음 단계.

+
+
+ +
+ +
+
+ + + diff --git a/docs/design/webmail/README.md b/docs/design/webmail/README.md index 02ae354..848e40c 100644 --- a/docs/design/webmail/README.md +++ b/docs/design/webmail/README.md @@ -2,10 +2,12 @@ 확정된 디자인 캔버스의 원본이다. 화면 구현의 기준으로 쓴다. -- 캔버스: https://claude.ai/artifact/WJvVsKvXepxn3p75CGJAr2 (확정본: 2026-10-26, 버전 11) +- 캔버스: https://claude.ai/artifact/WJvVsKvXepxn3p75CGJAr2 (확정본: 2026-10-27, 버전 14) - 화면: `Login`(로그인) · `Main`(분할 보기: 메일함·목록·본문) · `MainList`(목록 보기: 게시판형 표) · `MainListRead`(목록 보기에서 연 메일) - · `Compose`(메일 쓰기) · `States`(오류·빈 상태·피드백 모음) + · `Compose`(메일 쓰기 — 서식 편집기) · `ComposeEditor`(메일 쓰기 · 편집기 상태: 포커스·툴바·메뉴·팝오버·인용·이미지 첨부) + · `States`(오류·빈 상태·피드백 모음) - 파일 형식은 캔버스 전용(`` 마크업 + `DCLogic` 클래스)이다. 그대로 쓰지 말고 **구조·간격·색·문구의 기준**으로만 본다. + `Compose` 의 툴바·메뉴는 눌러 볼 수 있는 시안이다 — 버튼 상태만 바뀌고 본문 글자에 서식이 입혀지지는 않는다. ## 캔버스 안의 자산 주소 @@ -28,16 +30,119 @@ - 목록 보기 행: 1줄 표, 38px. 칸 = 별 32 · 보낸 사람 168 · 제목(나머지) · 날짜 84 · 크기 64 (px, 칸 사이 12). 폭 1100px 미만에서 크기 칸 숨김, 600px 미만에서 보낸 사람·날짜 축소, 더 좁으면 가로 스크롤. 캔버스는 파서 제약 때문에 grid 로 그렸지만 **구현은 실제 ``** 로 한다. - - 별 버튼·"분할 | 목록" 전환은 밀도를 위해 32~36px 로 둔다(클릭 영역 44px 기준의 의도된 예외 — 사용자 확정). +- **클릭 영역은 44px 이 기본**이다. 아래만 밀도를 위한 의도된 예외다(사용자 확정). + - 목록 표의 별 버튼(폭 32px) · "분할 | 목록" 전환(바깥 36px) + - 메일 쓰기 편집기(2026-10-27): 툴바 아이콘 버튼 40×44(새로고침·이전/다음 버튼과 같은 크기) · 메뉴 항목 높이 40(메일함 항목과 같다) + · 색 견본 칸 40×40 · 표 고르기 격자 칸 22(간격 3, 키보드는 화살표) - 보기 방식: 목록 머리의 "분할 | 목록" 전환. 선택은 사용자별로 기억한다. - **콘텐츠 열(읽기·쓰기 공통)**: 최대 폭 800px, 좌우 여백 32px, 위 20px·아래 36px, 본문 글줄 최대 680px, 영역 안 왼쪽 정렬. 분할 보기 읽기·목록 보기 읽기·쓰기 세 곳이 같은 값을 쓰고, 툴바 첫 아이콘도 같은 32px 선에서 시작한다. - 브랜드 컬러 "바다 청록": 바탕 `#0B3640`, 강조 `#0E7C86`. 파생 색은 각 화면 원본의 인라인 스타일을 따른다. +## 메일 쓰기 (2026-10-27 확정 — `Compose` · `ComposeEditor`) + +위에서부터 머리(제목·닫기) → 받는 사람(→ 참조·숨은참조) → 제목 → 본문 툴바 → 본문 → 첨부 → 보내기 줄이다. 모두 콘텐츠 열 안에 있다. +첨부 안내문은 "파일당 최대 20MB · 합계 25MB · 최대 20개 · 문서, 이미지, 압축 파일" 이다(용량은 서버 값, 개수는 계약 상수). + +### 글자 입력칸 포커스 — 한 가지 규칙 + +| 항목 | 값 | +| --- | --- | +| 대상 | 받는 사람 · 참조 · 숨은참조 · 제목 · 본문 편집기 | +| 위치 | 그 칸의 아래 hairline 자리. 본문은 편집 영역 아래(첨부 영역 위) | +| 두께 · 색 | 2px `--color-brand`(#0E7C86). 1px 선의 색을 바꾸고 안쪽 그림자 1px 를 더한다 — 높이가 변하지 않는다 | +| 모서리 | 없음(곧은 선) | +| 범위 | 행 전체 폭 — 원래 hairline 과 같은 길이(콘텐츠 열, 최대 800px). 이름표 아래까지 이어진다 | +| 언제 | 글자 입력 요소(input·편집 영역)에 포커스가 있을 때. 마우스로 눌러도 보인다(`:focus`) | +| 대비 | 흰 바탕 4.9:1 · 원래 선 #EEF0F3 4.3:1 · 오류 띠 #FEF3F2 4.5:1 (모두 3:1 이상) | + +- **버튼 고리와의 관계**: 같은 색·같은 2px 이고 모양만 다르다. 누르는 것(버튼·링크)은 전역 `:focus-visible` 고리(바깥 2px, 버튼 모서리를 따른다), + 쓰는 곳은 선이다. 행 안의 버튼(받는 사람 토큰 ✕ · "참조 · 숨은참조")에 포커스가 가면 고리만 보이고 행의 선은 회색 그대로다 — 한 번에 하나만 보인다. +- 테두리가 있는 입력칸(링크 팝오버·로그인)은 상자가 있으므로 버튼처럼 고리를 쓴다. +- 구현(tokens.css 변수): + + ```css + .compose-field:has(:is(input, .ProseMirror):focus) { + border-bottom-color: var(--color-brand); + box-shadow: inset 0 calc(var(--border-width) * -1) 0 var(--color-brand); + } + .compose-field :is(input, .ProseMirror):focus-visible { outline: none; } + ``` + + `.compose-field` 는 받는 사람·참조·숨은참조 행, 제목 행, 본문 편집 영역에 붙인다. 편집 영역이 아래 hairline 을 가지므로 첨부 영역의 위 선은 없앤다. + +### 본문 서식 편집기 (TipTap) + +- **툴바**: 한 줄 · 높이 52(위아래 4) · 왼쪽 여백 20 + 버튼 안 12 → 첫 아이콘이 32px 선. 버튼 사이 2, 묶음 사이 세로선 1×20 #E6E8EC(좌우 4). + - 아이콘 버튼 40×44 · 모서리 8 · 아이콘 16(선 1.75) · #344054. 글꼴 108×44 · 글자 크기 56×44(글자 + ▾). + - 상태: 올림 #F2F4F7 · 켜짐(`aria-pressed`) #E3F2F3 + 안쪽 1px #0E7C86 + 아이콘 #0A5F67(옅은 바탕만으로는 3:1 이 안 돼 선을 둔다) + · 비활성 50% · 포커스는 전역 고리 · 메뉴 열림(`aria-expanded`)은 올림과 같은 바탕. + - `role="toolbar"` — Tab 한 번에 들어가 화살표로 옮긴다. 본문에서 Alt+F10 → 툴바, Esc → 본문. +- **툴바 기능(800px)**: 실행 취소 · 다시 실행 | 글꼴 ▾ · 글자 크기 ▾ | 굵게 · 기울임 · 밑줄 | 글자 색 · 배경 색 | 글머리 목록 · 번호 목록 | 링크 | 더보기 +- **더보기(늘)**: 취소선 | 왼쪽 · 가운데 · 오른쪽 정렬 | 내어쓰기 · 들여쓰기 | 인용 · 표 넣기 › · 가로줄 | 서식 지우기 + - 항목 높이 40 · 아이콘 16 · 이름 · 단축키(12px #667085) · 켜진 것은 오른쪽 ✓. 값을 고르는 것(글꼴·크기·색·표)은 › 로 한 단계 더 연다. + - 단축키: 굵게 Ctrl+B · 기울임 Ctrl+I · 밑줄 Ctrl+U · 취소선 Ctrl+Shift+S · 글머리 Ctrl+Shift+8 · 번호 Ctrl+Shift+7 · 정렬 Ctrl+Shift+L/E/R + · 내어쓰기 Ctrl+[ · 들여쓰기 Ctrl+] · 인용 Ctrl+Shift+B · 링크 Ctrl+K · 서식 지우기 Ctrl+\ · 실행 취소 Ctrl+Z · 다시 실행 Ctrl+Y + - 맥은 Ctrl 대신 ⌘ 이고 표기도 기호로 보인다(⌘B · ⇧⌘S). 다시 실행은 맥 관례대로 ⇧⌘Z 로 적는다(⌘Y 도 된다). + 내어쓰기 · 들여쓰기 · 서식 지우기는 할 일이 없어도 키를 삼킨다 — 맥의 ⌘[ 가 브라우저 "뒤로"가 되어 쓰던 메일을 잃지 않게 (2026-10-27 구현하며 정함). +- **좁은 폭**: 툴바는 줄을 바꾸지 않는다. 묶음째 아래 순서로 더보기에 옮기고, 숨은 묶음은 더보기 맨 위에 툴바 순서대로 들어간다. 더보기 단추에 숫자를 달지 않는다. + 1. 실행 취소 · 다시 실행 — 쓰기 칸 734px 미만 (Ctrl+Z · Ctrl+Y 는 그대로) + 2. 글꼴 · 글자 크기 — 639px 미만 + 3. 글자 색 · 배경 색 — 460px 미만 + 4. 링크 — 365px 미만 (주소를 붙여 넣으면 저절로 링크가 된다) + 5. 글머리 · 번호 목록 — 312px 미만 + + 굵게 · 기울임 · 밑줄과 더보기는 늘 남는다. 폭은 지금 크기로 셈한 참고값이다 — 구현은 툴바의 실제 폭을 재어(ResizeObserver) 이 순서로 넘긴다. + `Compose` 보드의 좁은 폭 메뉴는 색 · 목록을 취소선 뒤에 그렸지만, 이 문서의 순서(숨은 묶음 전부를 맨 위에)가 기준이다 — 구현도 이 순서다. + 휴대폰에서는 더보기 메뉴가 화면 높이에 맞춰 안에서 스크롤된다. +- **글꼴**: 맑은 고딕(기본) · 굴림 · 돋움 · 바탕 | Arial · Verdana · Georgia · Times New Roman · Courier New. 목록의 이름은 그 글꼴로 보인다. + - 본문 기본 글꼴은 맑은 고딕 스택 `'Malgun Gothic', '맑은 고딕', 'Apple SD Gothic Neo', sans-serif` 다 — 편집기 안 모양을 받는 사람이 보는 모양과 같게 한다. + 화면 UI 는 Pretendard 그대로다. 맥에서는 Apple SD 산돌고딕 Neo 로 보인다. + - "맑은 고딕(기본)"을 고르면 글꼴 지정을 지운다. +- **글자 크기(px)**: 12 · 13 · 14(기본) · 16 · 18 · 20 · 24 · 32. 제목 스타일(제목 1·2·3)은 두지 않는다. 고른 글자의 크기가 섞이면 "–". +- **색 팔레트**(8색씩 · 견본 칸 40×40, 보이는 색 26 · 고른 색에 ✓ · 고른 색 이름을 글자로도 보인다): + - 글자 색: 기본 #1F2733 · 회색 #667085 · 빨강 #B42318 · 주황 #B54708 · 초록 #067647 · 청록 #0E7C86 · 파랑 #175CD3 · 보라 #6941C6 + — 모두 흰 바탕 4.5:1 이상. "기본"은 글자 색을 지운다. + - 배경 색: 없음 · 노랑 #FFF1A8 · 주황 #FDE4CC · 분홍 #FBDCE3 · 초록 #D9F2DF · 하늘 #D6E8FB · 보라 #E9E0FA · 회색 #E9EBEF + — 본문 글자(#1F2733)가 10:1 이상 남는다. "없음"은 배경 색을 지운다. +- **붙여 넣은 글 · 원문 인용의 서식**: 보낼 수 있는 형식(계약 2절 "서식 본문")만 받고 나머지는 버린다 — 색 이름(red) · Calibri 같은 서식은 + 기본 모양으로 보인다. 편집기에 보이는 모양이 곧 받는 사람이 보는 모양이다 (2026-10-27 구현하며 정함). +- **링크** 팝오버(폭 300): 주소 · 표시할 글자(비우면 주소). 새 링크는 [취소] [넣기], 이미 있는 링크는 [링크 빼기] … [취소] [적용]. + Enter = 적용, Esc = 닫고 본문으로. 주소는 http(s) · mailto 만 받는다. +- **표 넣기**(더보기 ›): 8열 × 6행 격자에서 고른다("3행 × 4열"). 표 안의 행·열 추가·삭제는 다음 단계. +- **메뉴·팝오버 공통**: 흰 바탕 · 테두리 #E6E8EC · 모서리 10 · 그림자 `--shadow-toast` · 툴바 아래 선 바로 밑에서 연다. Esc 로 닫는다. +- **빈 본문**: 안내 문구 "내용을 입력하세요" · #667085(4.9:1). 커서는 문구 앞에 선다. +- **문단**: 문단 사이 여백은 0 — Enter 한 번이 한 줄이다(지금 textarea 와 같다). 줄 간격은 1.75 이고, 보내는 본문도 같은 값이다 + (감싸는 div 의 `line-height` — 받는 사람이 편집기와 같은 모양으로 본다, 2026-10-27 구현하며 정함). + 여러 칸 띄어쓰기 · 줄 앞 공백도 받는 쪽에서 그대로 보이게 보낸다. 탭은 다음 8칸 자리까지의 공백이 된다(편집 영역의 탭 간격과 같다, 2026-10-27). + +### 답장 · 전달의 원문 인용 + +- 원문은 인용 막대(왼쪽 2px #D0D5DD · 막대와 글 사이 14) 안에 둔다. 원문 글은 #4A5363, 내 글과 24 띄운다. +- 머리는 답장 · 전달 모두 **"원본 메일"**(12.5px 굵게)이고, 그 아래에 항목 이름(#667085) · 값 표를 둔다. + - 답장: 보낸 사람 · 날짜 · 제목 — 커서는 맨 위 첫 줄 + - 전달: 보낸 사람 · 날짜 · 받는 사람 · 참조 · 제목 — 커서는 받는 사람 칸 +- 막대는 회색이다 — 2px 브랜드색 선은 포커스 표시라 겹치지 않게 한다. 툴바의 "인용"도 같은 막대를 머리 없이 쓴다. +- 초안이 한도(제목 255자 · 본문 텍스트 100,000자 · 서식 본문 500,000자 — 계약 2절)를 넘으면 인용을 줄이고 인용 끝에 "(원문이 길어 이하 생략)"을 둔다(2026-10-26 구현하며 정함 — 캔버스에는 그리지 않았다). + - 처음 맞출 때 내 글 몫으로 한도의 5%를 남긴다. 보내기 직전에 다시 재서 넘으면 인용을 더 줄이고, 편집기에도 줄인 모양을 보인다(실행 취소로 되돌릴 수 있다). + - 내 글만으로 넘으면 보내지 않는다 — 본문 아래(첨부 위)에 받는 사람 오류와 같은 빨간 띠로 알리고 본문에 초점을 준다. + 제목이 255자를 넘으면 제목 아래에 같은 띠를 둔다 (2026-10-27 구현하며 정함). + +### 본문 이미지 → 첨부 파일 + +- 본문에는 이미지를 넣지 않는다(사용자 확정). 툴바에 이미지 버튼이 없다. +- 본문에 붙여 넣거나(Ctrl+V) 끌어다 놓은 이미지는 첨부 파일로 올린다. 끌어다 놓은 다른 파일도 첨부로 올린다(쓰기 화면 어디에 놓아도 같다). + 한도는 첨부와 같다 — 넘치면 첨부 목록에 실패로 보인다(`States` 의 "첨부 업로드"). + - 클립보드에 **글이 함께 있으면 글을 붙여 넣는다** — 워드 · 엑셀에서 복사하면 고른 부분의 그림이 글과 함께 따라오기 때문이다. + 글 없이 파일만 있을 때 첨부로 돌린다 (2026-10-27 구현하며 정함 — 캔버스 시안은 그림이 있으면 늘 첨부로 돌린다). + - 이름 없이 붙여 넣은 그림은 "붙여넣은 이미지 N.확장자"로 올린다. 원래 이름이 있는 파일은 그 이름 그대로다. +- 끌어다 놓는 동안 본문 위에 점선 덮개(2px #0E7C86 점선 · 바탕 #F3F8F8)와 "놓으면 첨부 파일로 올립니다"를 보인다. + 쓰기 화면이 떠 있으면 파일은 창 어디(앱 레일 위 포함)에 놓아도 첨부로 받고, 덮개도 파일이 창 위에 있는 동안 보인다 — + 받지 않는 곳에 놓으면 브라우저가 그 파일을 열어 쓰던 메일을 잃기 때문이다 (2026-10-27 구현하며 정함). +- 이미지를 첨부로 돌렸을 때만 성공 알림 "이미지는 본문 대신 첨부 파일로 올렸습니다."를 띄운다(성공 알림과 같은 모양 · 5초). + ## 구현하며 정한 보완 (2026-10-26 — 캔버스에는 아직 없다) -- 쓰기 화면 첨부 안내문에 합계·개수 한도를 함께 적는다: "파일당 최대 20MB · 합계 25MB · 최대 20개 · 문서, 이미지, 압축 파일" (용량은 서버 값, 개수는 계약 상수). -- 답장·전달 초안이 한도(제목 255자·본문 100,000자)를 넘으면 인용을 줄이고 끝에 "(원문이 길어 이하 생략)"을 둔다. - 찾지 못한 첨부(보관 시간이 지난 업로드·지워진 원본)는 실패로 표시하고, 전달 원본에는 "빼기"만 둔다(다시 올릴 수 없다). - 없는 메일함·메일(주소창에 남은 옛 id 등)에는 "다시 시도" 대신 "받은편지함으로"·"목록으로"를 둔다. "다시 시도"는 메일 서버 장애처럼 다시 해 보면 나아질 수 있는 오류에만 쓴다. diff --git a/docs/design/webmail/canvas.json b/docs/design/webmail/canvas.json index 4adf2a0..6d41e4e 100644 --- a/docs/design/webmail/canvas.json +++ b/docs/design/webmail/canvas.json @@ -10,9 +10,10 @@ "Compose.dc.html": { "x": 3040, "y": 0, "w": 1440, "h": 900, "title": "메일 쓰기", "expand": "fill", "is_interactive": true }, "States.dc.html": { "x": 0, "y": 1020, "w": 1440, "h": 1040, "title": "오류 · 빈 상태 · 피드백" }, "MainList.dc.html": { "x": 1520, "y": 1020, "w": 1440, "h": 900, "title": "메일함 · 목록 보기", "expand": "fill", "is_interactive": true }, - "MainListRead.dc.html": { "x": 3040, "y": 1020, "w": 1440, "h": 900, "title": "목록 보기 · 메일 읽기", "expand": "fill", "is_interactive": true } + "MainListRead.dc.html": { "x": 3040, "y": 1020, "w": 1440, "h": 900, "title": "목록 보기 · 메일 읽기", "expand": "fill", "is_interactive": true }, + "ComposeEditor.dc.html": { "x": 4560, "y": 0, "w": 1440, "h": 3576, "title": "메일 쓰기 · 편집기 상태" } }, - "order": ["Main.dc.html", "Login.dc.html", "Compose.dc.html", "States.dc.html", "MainList.dc.html", "MainListRead.dc.html"], + "order": ["Main.dc.html", "Login.dc.html", "Compose.dc.html", "States.dc.html", "MainList.dc.html", "MainListRead.dc.html", "ComposeEditor.dc.html"], "notes": { "flow": { "x": 0, "y": -300, "text": "arasystem 웹메일 — 1단계 화면 (로그인 → 메일함 → 쓰기)", "kind": "title1", "maxW": 4480 } }, diff --git a/docs/mail/api-contract.md b/docs/mail/api-contract.md index 8677cfc..defac3b 100644 --- a/docs/mail/api-contract.md +++ b/docs/mail/api-contract.md @@ -150,6 +150,12 @@ interface Attachment { id: string; name: string; type: string; size: number } 본문 안 인라인 이미지(`cid:`)는 작은 것만 `data:` URI 로 바꿔 넣고 나머지는 지운다(첨부 목록에는 남는다). **클라이언트는 그래도 격리된 iframe 에만 그린다** (스크립트 비허용 sandbox + 내부 CSP — 이중 방어). +**읽기 한도**: 상세 조회는 본문 한 부분을 UTF-8 2,000,000바이트까지 읽는다 — 넘는 부분은 잘린 채 온다(잘렸다는 표시는 아직 없다). +HTML 은 정화하기 **전에** 길이(HTML 부분이 여럿이면 이어 붙인 것이 2,000,000자 이하)와 파서 비용을 본다 — +약 2만 단을 넘는 중첩이나, 깊게 열린 태그 아래로 짝 없는 닫는 태그를 아주 많이 둔 HTML 은 정화가 서버를 수십 초씩 붙잡기 때문이다. +넘으면 정화하지 않고 `htmlBody: null`·`hasBlockedImages: false` 로 내려준다. `textBody` 는 그대로라 클라이언트는 그것을 보여 주고 +답장·전달 인용도 그것으로 만든다 (HTML 만 있는 메일이면 둘 다 `null`). 수백 단의 인용 스레드·2MB 짜리 큰 표·Outlook 조건부 주석은 한도 안이다. + ### 엔드포인트 | 메서드 · 경로 | 요청 | 성공 | 주요 실패 | @@ -173,7 +179,7 @@ interface Attachment { id: string; name: string; type: string; size: number } | 받는 사람 합계 100명 초과 (배열 하나가 100을 넘으면 `VAL_001`) | 400 `MAIL_002` | | 엔진에서 찾을 수 없는 첨부 id — 올려 두고 오래 지나 만료된 업로드, 그사이 지워진 원본 메일의 첨부 등 | 400 `MAIL_004` — 클라이언트는 해당 첨부를 실패로 표시한다. 올린 파일은 다시 올리게, 전달하는 원본 첨부는 빼게 한다 (원본은 다시 올릴 수 없다) | | 첨부 20개 초과 | 400 `MAIL_006` — 클라이언트는 첨부를 추가할 때 미리 막는다 | -| JSON 본문 1MB 초과 | 413 `FILE_002` | +| JSON 본문 1MB 초과 (발송 `POST /api/messages` 만 4MB — 서식 본문과 긴 인용 때문) | 413 `FILE_002` | 메일함 정렬(확정 디자인 기준): 받은편지함 → 보낸편지함 → 임시보관함 → 스팸메일함 → 휴지통 → 보관함 → 그 외(`sortOrder`, 이름 순). @@ -202,11 +208,13 @@ interface MailQuota { - 같은 키인데 **내용이 다르면**(실패 뒤 받는 사람·제목·본문·첨부를 고친 경우) 보내지 않고 409 `MAIL_005`. 첫 메일이 실제로 나갔을 수 있으므로, 클라이언트는 사용자에게 보낸편지함을 확인하게 하고 **새 키**로 다시 보낼 수 있게 한다. 서버는 첫 발송 때 내용 지문(`Idempotency-Key` 를 키로 한 HMAC — 받는 사람은 키를 모른다)을 함께 남겨 비교한다. - - 지문에 들어가는 것: 받는 사람·참조·숨은참조의 **주소**(대소문자·순서 무시), 제목, 본문(줄바꿈 정규화), 첨부 id(순서 무시), `inReplyTo`. + - 지문에 들어가는 것: 받는 사람·참조·숨은참조의 **주소**(대소문자·순서 무시), 제목, 본문(줄바꿈 정규화), 서식 본문(서버가 정화한 HTML), 첨부 id(순서 무시), `inReplyTo`. - 들어가지 않는 것: 표시 이름, 첨부 이름 — 이것만 바꾼 재시도는 "같은 내용"으로 보고 처음 결과를 돌려준다. - **알려진 한계** (1단계는 발송 기록을 따로 저장하지 않고 보낸편지함에서 찾는다): - 첫 발송 뒤 보낸 사본을 다른 메일함으로 옮기거나 지웠으면, 또는 그사이 보낸편지함·임시보관함에 200통 넘게 쌓였으면 같은 키의 재시도가 **다시 나갈 수 있다**. - "처리 중" 표시는 서버 메모리에 있어 서버가 재시작되면 사라진다. 엔진 장애 직후의 드문 경합에서도 두 번 나갈 수 있다. + - 서식 본문의 지문은 **정화한 결과**로 만든다 — 첫 발송과 재시도 사이에 서버의 정화 규칙이 바뀌면(배포) 같은 키의 재시도가 + `MAIL_005` 로 막힐 수 있다. 그때도 위 409 처리(보낸편지함 확인 → 새 키)를 따른다. - 이 한계를 없애려면 (사용자, 키) → (지문, 메일 id) 를 서버 DB 에 저장한다 — 서버를 여러 대로 늘릴 때 함께 검토한다. ```jsonc @@ -215,7 +223,8 @@ interface MailQuota { "cc": [], // 선택 "bcc": [], // 선택 (받는 사람 합계 100 이하) "subject": "Re: 메일 서버 이전 일정 공유", // 255자 이하 - "body": "도윤님, 확인했습니다.\n…", // 일반 텍스트, 100,000자 이하 + "body": "도윤님, 확인했습니다.\n…", // 일반 텍스트(서식 본문의 대체 본문), 100,000자 이하 + "bodyHtml": "

도윤님, 확인했습니다.

…
", // 선택 — 서식 편집기 본문, 500,000자 이하 (아래 "서식 본문") "attachments": [{ "id": "<업로드 응답 id>", "name": "체크리스트.xlsx" }], // 선택, 20개 이하 (넘으면 MAIL_006) "inReplyTo": "<답장할 메일의 id>" // 선택 — 스레드 헤더를 서버가 채운다 } @@ -223,10 +232,44 @@ interface MailQuota { **전달**: 원본 메일 `attachments[].id` 를 발송 본문의 `attachments` 에 그대로 넣으면 원본 첨부가 다시 붙는다 (같은 계정 안의 파일이라 다시 올릴 필요가 없다). 제목 `Fwd:` 와 인용 본문은 클라이언트가 만들고, `inReplyTo` 는 보내지 않는다. +발송 본문의 첨부 이름(`attachments[].name`)은 업로드한 파일과 같은 규칙으로 확장자를 허용 목록에서 검사한다(밖이면 400 `FILE_001`) — 원본 첨부를 그대로 전달할 때도 같다. -**답장·전달 초안의 길이**: 클라이언트가 초안을 만들 때 한도에 맞춘다 — 제목은 255자, 본문은 100,000자. +**답장·전달 초안의 길이**: 클라이언트가 초안을 만들 때 한도에 맞춘다 — 제목은 255자, 본문은 텍스트 100,000자·서식 본문 500,000자. 넘으면 인용을 줄이고 끝에 생략 표시를 둔다 (넘긴 채 보내면 400 `VAL_001`). -업로드한 파일과 같은 규칙으로 이름의 확장자를 허용 목록에서 검사한다(밖이면 400 `FILE_001`). +처음 맞출 때는 쓸 글 몫으로 한도의 5%를 남기고(텍스트 95,000자·서식 475,000자), 보내기 직전에 실제로 보낼 본문을 다시 재서 넘으면 인용을 더 줄인다. +인용 없이 쓴 글만으로 넘으면 보내지 않고 쓰기 화면에서 알린다. + +**서식 본문 (`bodyHtml`)**: 쓰기 화면의 서식 편집기가 만든 HTML 이다. +- 클라이언트는 같은 내용의 일반 텍스트를 `body` 로 **함께** 보낸다 (받는 쪽 프로그램이 텍스트만 볼 때 쓰는 대체 본문 — + 목록은 `- `/`1. `, 인용은 `> ` 를 붙인다). 서버는 둘을 `multipart/alternative` 로 보낸다. `bodyHtml` 이 없으면 텍스트 메일이다. +- 서버는 보내기 전에 **허용 목록으로 정화**한다 — 편집기가 만드는 것만 남긴다: + 문단·줄바꿈, 굵게·기울임·밑줄·취소선, 글자 색·배경 색·글꼴·글자 크기(span 의 style), 정렬·들여쓰기·줄 간격, 목록, 인용, 가로줄, + 표(칸 폭을 적는 `colgroup`·`col` 포함), 링크(`http`·`https`·`mailto`). + 그 밖의 태그·속성·스타일·스크립트는 지운다. 지문에는 정화한 결과가 들어간다. + - 남는 속성: `style`(`a` 를 뺀 모든 태그), `a` 의 `href`(링크에는 이것만 남는다 — 링크 글자 색은 안쪽 span 의 style 로), `ol` 의 `start`, + `td`·`th` 의 `colspan`·`rowspan`(숫자만). + - 남는 style: `color`·`background-color`·`font-family`·`font-size`·`font-weight`·`line-height`·`text-align`·`margin`·`padding`(각 변 포함)· + `border`(각 변·`border-color`·`border-style`·`border-width`·`border-collapse`)·`width`·`min-width`·`vertical-align`·`white-space`. + - style 은 **값의 형식까지** 맞아야 남는다 — 형식이 틀린 선언은 그 선언만 지운다: + 글자 크기는 `px` 만, 색은 `#rgb`·`#rrggbb`·`rgb()`·`rgba()`(색 이름·`hsl()` 은 지운다), 여백은 `0` 또는 `px`(음수 없음), + 줄 간격은 단위 없는 숫자 1~3(소수 둘째 자리까지), 글꼴은 쓰기 화면의 글꼴 목록과 흔한 대체 이름(`Helvetica`·`Times`·`Courier`· + `AppleGothic`·`AppleMyungjo`)·일반 이름(`sans-serif`·`serif`·`monospace`)으로만 이뤄진 목록. + - 형광 표시는 `mark` 가 아니라 span 의 `background-color` 로 보낸다 (`mark`·`h1` 같은 모르는 태그는 태그만 지우고 글자는 남긴다). + - 정화하고 남는 것이 없으면 `body` 만으로 텍스트 메일을 보낸다. + - 한도 — 넘으면 400 `VAL_001`. 엔진·첨부 조회 **전**이라 메일은 나가지 않으므로, 고친 뒤 **같은 키**로 다시 보내면 된다. + 정화 전에는 받은 메일과 같은 파서 비용 한도(위 "읽기 한도")를, 정화 뒤에는 결과가 UTF-8 2,000,000바이트 이하인지 본다 + (우리 웹메일이 다시 읽을 때 잘리지 않게). 500,000자 안이어도 넘을 수 있다 — 정화가 `&` 를 `&`(5바이트)로 바꾸고, + 한글은 글자당 3바이트·이모지는 4바이트다. +- 기본 글꼴은 **맑은 고딕** 이다 — 클라이언트가 본문 전체를 `font-family`·`font-size:14px`·`line-height:1.75` 를 가진 `div` 하나로 감싸 보낸다. + 편집기 화면의 CSS 는 메일에 따라가지 않으므로 문단 여백(0)·인용 막대·표 테두리도 각 요소의 인라인 `style` 로 보낸다. +- 클라이언트도 **같은 값 형식만 읽는다** — 붙여 넣은 글과 원문 인용을 편집기로 옮길 때 위 형식 밖의 서식(색 이름·`font-size:0`·목록에 없는 글꼴 등)은 + 버린다 (글자 크기의 `pt` 는 `px` 로 바꾼다). 서버만 지우면 편집기에서 숨은 글이 받는 사람에게 보이는 식으로 화면과 보낸 메일이 달라진다. +- 여러 칸 띄어쓰기와 줄 앞 공백은 받는 쪽이 합치거나 지우므로 nbsp 로 바꿔 보낸다 (보통 공백과 번갈아 — 긴 공백에서도 줄은 바뀐다). + 탭은 다음 8칸 자리까지의 공백으로 바꾼다. 텍스트 대체 본문(`body`)은 그대로 둔다. +- 우리 웹메일이 다시 읽을 때(보낸편지함·받은 사람)도 이 서식이 남아야 한다 — 받은 메일 정화(`htmlBody`)의 허용 목록이 위 서식을 포함한다. +- 답장·전달의 원문 인용은 두 경우 모두 **"원본 메일"** 머리(보낸 사람·날짜·제목, 전달은 받는 사람·참조도)와 회색 인용 막대로 만든다. + 원문은 `htmlBody`(없으면 `textBody`)를 편집기로 옮기며, 편집기가 모르는 요소(이미지 등)는 빠진다. +- **본문 안 이미지는 이번 단계에서 넣지 않는다** — 붙여 넣거나 끌어다 놓은 이미지는 첨부 파일로 올린다 (첨부 한도 규칙 그대로). ### 첨부 업로드 한도 @@ -241,4 +284,4 @@ interface MailQuota { ### 이 단계에서 하지 않는 것 -실시간 새 메일 알림(SSE), 검색 화면, 임시 저장(Drafts 쓰기), 서식 있는 편집기, 외부 이미지 보기(이미지 중계), 다중 선택 일괄 처리. +실시간 새 메일 알림(SSE), 검색 화면, 임시 저장(Drafts 쓰기), 본문 안 이미지(인라인 cid — 붙여 넣은 이미지는 첨부로), 외부 이미지 보기(이미지 중계), 다중 선택 일괄 처리.