docs: 14단계 화면(검색 · 대화 묶기)을 확정해 디자인 문서에 옮긴다
CI / Backend (lint · type-check · test · e2e · build) (pull_request) Successful in 13m45s
CI (Flutter) / Flutter (analyze · test) (pull_request) Successful in 20m40s
CI / Frontend (lint · type-check · test · build) (pull_request) Successful in 12m31s
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (pull_request) Successful in 1m45s
CI (Flutter) / Flutter (analyze · test) (push) Successful in 20m0s
CI / 에러 코드 SSOT 동기화 검사 (pull_request) Successful in 11m9s
CI / Backend (lint · type-check · test · e2e · build) (push) Has been cancelled
CI / Frontend (lint · type-check · test · build) (push) Has been cancelled
CI / Infra (compose · Dockerfile · nginx · 운영 스크립트) (push) Has been cancelled
CI / 에러 코드 SSOT 동기화 검사 (push) Has been cancelled

사용자가 캔버스 초안을 확정했다(2026-11-05). 자체 색인의 부분 일치 · 조건 검색 · 검색어 강조, 읽기 화면의
같은 대화, 목록의 대화로 묶어 보기(사용자마다 · 기본 끔)를 README 14단계 절로 적고 보드 셋과 canvas.json 을
캔버스(v52)와 맞춘다. 엔진 스레드가 회신: · 답장: · 전달: 을 떼지 않고 검색이 어절 통째 일치뿐이라는
11-03 실측이 까닭이다. API 규칙은 구현 때 계약 2절에 더하고, 구현 · 배포는 전환 뒤 안정화 다음에 한다.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit was merged in pull request #17.
This commit is contained in:
2026-11-05 12:48:21 +09:00
parent 1171593724
commit ba80956d7e
5 changed files with 1166 additions and 3 deletions
+351
View File
@@ -0,0 +1,351 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<title>목록 · 대화로 묶어 보기</title>
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<style>
@font-face{font-family:'Pretendard';src:url('/_blob/16067b9aa5137abf808e3dbef30c42e0') format('woff2-variations'),url('/_blob/16067b9aa5137abf808e3dbef30c42e0') format('woff2');font-weight:45 920;font-style:normal;font-display:swap}
body{margin:0}
a{color:#0E7C86}a:hover{color:#0A5F67}
:focus-visible{outline:2px solid #0E7C86;outline-offset:2px}
.row:hover{background:#F8F9FB}
</style>
</helmet>
<div style="min-height: 100vh; display: flex; background: #FFFFFF; color: #171C26; font-family: Pretendard, -apple-system, 'Apple SD Gothic Neo', 'Malgun Gothic', system-ui, sans-serif; font-size: 13px; line-height: 1.5">
<nav aria-label="사내 시스템" style="flex: 0 0 72px; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; gap: 14px; padding: 10px 0 12px; background: #0B3640">
<a href="Main.dc.html" style="display: inline-flex; align-items: center; justify-content: center; width: 60px; min-height: 44px; border-radius: 10px; text-decoration: none">
<img src="/_blob/2b9ef4349333debc969432c859eaf1bf" alt="ARA SYSTEM" width="48" height="17" style="display: block; width: 48px; height: auto">
</a>
<ul style="list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px">
<li>
<a href="Main.dc.html" aria-current="page" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; width: 60px; min-height: 56px; border-radius: 10px; background: #155A64; color: #FFFFFF; font-size: 11px; font-weight: 600; text-decoration: none">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
<span>메일</span>
</a>
</li>
<li>
<a href="#" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; width: 60px; min-height: 56px; border-radius: 10px; color: #A3C2C6; font-size: 11px; font-weight: 500; text-decoration: none">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
<span>캘린더</span>
</a>
</li>
<li>
<a href="#" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; width: 60px; min-height: 56px; border-radius: 10px; color: #A3C2C6; font-size: 11px; font-weight: 500; text-decoration: none">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
<span>파일</span>
</a>
</li>
<li>
<a href="#" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; width: 60px; min-height: 56px; border-radius: 10px; color: #A3C2C6; font-size: 11px; font-weight: 500; text-decoration: none">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
<span>주소록</span>
</a>
</li>
</ul>
<a href="Settings.dc.html" style="margin-top: auto; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; width: 60px; min-height: 56px; border-radius: 10px; color: #A3C2C6; font-size: 11px; font-weight: 500; text-decoration: none">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
<span>설정</span>
</a>
</nav>
<div style="flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column">
<header style="display: flex; align-items: center; flex-wrap: wrap; gap: 8px 16px; box-sizing: border-box; min-height: 56px; padding: 6px 12px 6px 20px; border-bottom: 1px solid #E6E8EC">
<h1 style="margin: 0; font-size: 16px; font-weight: 700; letter-spacing: -0.01em">메일</h1>
<form role="search" aria-label="메일 검색" style="flex: 1 1 280px; max-width: 520px; display: flex; align-items: center; gap: 8px; box-sizing: border-box; height: 40px; margin: 0; padding: 0 12px; border: 1px solid #D0D5DD; border-radius: 8px; background: #FFFFFF">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#667085" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
<input type="search" aria-label="메일 검색" placeholder="메일 검색" style="flex: 1 1 auto; min-width: 0; border: 0; outline: none; background: transparent; font: inherit; color: #171C26">
</form>
<div style="margin-left: auto; display: flex; align-items: center; gap: 10px">
<span aria-hidden="true" style="display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 50%; background: #E3F2F3; color: #0A5F67; font-size: 13px; font-weight: 600">김</span>
<div style="display: flex; flex-direction: column; line-height: 1.3">
<span style="font-weight: 600">김아라</span>
<span style="font-size: 12px; color: #667085">ara.kim@arasystem.it.kr</span>
</div>
<a href="Login.dc.html" style="display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 10px; border-radius: 8px; color: #4A5363; font-weight: 500; text-decoration: none">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>
<span>로그아웃</span>
</a>
</div>
</header>
<div style="flex: 1 1 auto; display: flex; flex-wrap: wrap; align-items: stretch">
<nav aria-label="메일함" style="flex: 1 1 208px; box-sizing: border-box; display: flex; flex-direction: column; gap: 14px; padding: 14px 10px; background: #F8F9FB; border-right: 1px solid #E6E8EC">
<a href="Compose.dc.html" style="display: flex; align-items: center; justify-content: center; gap: 8px; min-height: 44px; border-radius: 8px; background: #0E7C86; color: #FFFFFF; font-size: 13.5px; font-weight: 600; text-decoration: none">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path></svg>
<span>메일 쓰기</span>
</a>
<ul style="list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 1px">
<li><a href="Main.dc.html" aria-current="page" style="display: flex; align-items: center; gap: 10px; min-height: 40px; box-sizing: border-box; padding: 0 10px; border-radius: 7px; background: #E3F2F3; color: #0A5F67; font-weight: 600; text-decoration: none"><span style="flex: 1 1 auto">받은편지함</span><span style="font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums">2</span></a></li>
<li><a href="#" style="display: flex; align-items: center; min-height: 40px; box-sizing: border-box; padding: 0 10px; border-radius: 7px; color: #344054; font-weight: 500; text-decoration: none">보낸편지함</a></li>
<li><a href="#" style="display: flex; align-items: center; min-height: 40px; box-sizing: border-box; padding: 0 10px; border-radius: 7px; color: #344054; font-weight: 500; text-decoration: none">임시보관함</a></li>
<li><a href="#" style="display: flex; align-items: center; min-height: 40px; box-sizing: border-box; padding: 0 10px; border-radius: 7px; color: #344054; font-weight: 500; text-decoration: none">스팸메일함</a></li>
<li><a href="#" style="display: flex; align-items: center; min-height: 40px; box-sizing: border-box; padding: 0 10px; border-radius: 7px; color: #344054; font-weight: 500; text-decoration: none">휴지통</a></li>
</ul>
</nav>
<main style="flex: 999 1 560px; min-width: 0; display: flex; flex-wrap: wrap; align-items: stretch">
<section aria-labelledby="list-title" style="flex: 1 1 360px; min-width: 0; box-sizing: border-box; display: flex; flex-direction: column; border-right: 1px solid #E6E8EC">
<div style="display: flex; align-items: center; flex-wrap: wrap; gap: 6px 8px; min-height: 52px; box-sizing: border-box; padding: 4px 8px 4px 16px; border-bottom: 1px solid #E6E8EC">
<h2 id="list-title" style="margin: 0; font-size: 15px; font-weight: 700; letter-spacing: -0.01em">받은편지함</h2>
<span style="font-size: 12px; color: #667085">{{ headCount }}</span>
<button type="button" aria-pressed="{{ groupedStr }}" onClick="{{ toggleGroup }}" style="margin-left: auto; display: inline-flex; align-items: center; gap: 6px; min-height: 36px; box-sizing: border-box; padding: 0 10px; border: 1px solid {{ groupBorder }}; border-radius: 8px; background: {{ groupBg }}; color: {{ groupFg }}; font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
<span>대화로 묶기</span>
</button>
</div>
<ul aria-label="{{ listLabel }}" style="list-style: none; margin: 0; padding: 0">
<sc-for list="{{ rows }}" as="r" hint-placeholder-count="6">
<li style="border-bottom: 1px solid #EEF0F3; background: {{ r.bg }}">
<button type="button" onClick="{{ r.pick }}" style="width: 100%; display: flex; flex-direction: column; justify-content: center; gap: 1px; min-height: 52px; box-sizing: border-box; padding: 6px 16px; border: 0; background: transparent; font: inherit; line-height: 1.4; color: inherit; text-align: left; cursor: pointer">
<span style="display: flex; align-items: center; gap: 6px; width: 100%">
<span style="min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: {{ r.fw }}">{{ r.who }}</span>
<sc-if value="{{ r.hasCount }}" hint-placeholder-val="{{ false }}"><span style="flex: 0 0 auto; font-size: 12px; font-weight: 500; color: #667085; font-variant-numeric: tabular-nums">{{ r.countText }}</span></sc-if>
<span style="margin-left: auto; flex: 0 0 auto; font-size: 12px; color: {{ r.timeColor }}; font-weight: {{ r.timeFw }}; font-variant-numeric: tabular-nums">{{ r.time }}</span>
</span>
<span style="display: flex; align-items: center; gap: 6px; min-width: 0; width: 100%">
<span style="min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: {{ r.subFw }}">{{ r.subject }}</span>
<sc-if value="{{ r.hasAtt }}" hint-placeholder-val="{{ false }}"><svg role="img" aria-label="첨부 있음" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#667085" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex: 0 0 auto"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg></sc-if>
</span>
</button>
</li>
</sc-for>
</ul>
</section>
<div style="flex: 999 1 480px; min-width: 0; box-sizing: border-box; display: flex; flex-direction: column">
<article aria-labelledby="mail-subject" style="display: flex; flex-direction: column">
<div style="border-bottom: 1px solid #E6E8EC"><div style="display: flex; flex-wrap: wrap; align-items: center; gap: 2px; min-height: 52px; max-width: 800px; box-sizing: border-box; padding: 4px 32px 4px 22px">
<a href="Compose.dc.html" style="display: inline-flex; align-items: center; gap: 6px; min-height: 44px; box-sizing: border-box; padding: 0 10px; border-radius: 8px; color: #344054; font-weight: 500; text-decoration: none">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 14 4 9 9 4"></polyline><path d="M20 20v-7a4 4 0 0 0-4-4H4"></path></svg>
<span>답장</span>
</a>
<a href="Compose.dc.html" style="display: inline-flex; align-items: center; gap: 6px; min-height: 44px; box-sizing: border-box; padding: 0 10px; border-radius: 8px; color: #344054; font-weight: 500; text-decoration: none">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="7 17 2 12 7 7"></polyline><polyline points="12 17 7 12 12 7"></polyline><path d="M22 18v-2a4 4 0 0 0-4-4H7"></path></svg>
<span>전체 답장</span>
</a>
<a href="Compose.dc.html" style="display: inline-flex; align-items: center; gap: 6px; min-height: 44px; box-sizing: border-box; padding: 0 10px; border-radius: 8px; color: #344054; font-weight: 500; text-decoration: none">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 14 20 9 15 4"></polyline><path d="M4 20v-7a4 4 0 0 1 4-4h12"></path></svg>
<span>전달</span>
</a>
</div></div>
<div style="display: flex; flex-direction: column; gap: 18px; max-width: 800px; box-sizing: border-box; padding: 20px 32px 36px">
<h1 id="mail-subject" style="margin: 4px 0 0; font-size: 20px; line-height: 1.4; font-weight: 650; letter-spacing: -0.015em">{{ m.subject }}</h1>
<div style="display: flex; align-items: flex-start; gap: 12px">
<span aria-hidden="true" style="flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: 50%; background: #E3F2F3; color: #0A5F67; font-size: 14px; font-weight: 600">{{ m.initial }}</span>
<div style="flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px">
<div style="display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px">
<span style="font-size: 13.5px; font-weight: 600">{{ m.fromName }}</span>
<span style="font-size: 12.5px; color: #667085">{{ m.fromEmail }}</span>
<span style="margin-left: auto; font-size: 12.5px; color: #667085; white-space: nowrap; font-variant-numeric: tabular-nums">{{ m.date }}</span>
</div>
<span style="font-size: 12.5px; color: #4A5363">받는 사람 김아라 &lt;ara.kim@arasystem.it.kr&gt;</span>
</div>
</div>
<div style="white-space: pre-line; font-size: 14px; line-height: 1.75; color: #1F2733; max-width: 680px">{{ m.body }}</div>
<sc-if value="{{ m.hasConv }}" hint-placeholder-val="{{ true }}">
<section aria-labelledby="conv-title" style="display: flex; flex-direction: column; gap: 8px; padding-top: 16px; border-top: 1px solid #EEF0F3">
<h2 id="conv-title" style="margin: 0; font-size: 12.5px; font-weight: 600; color: #4A5363">같은 대화 {{ m.convCount }}통</h2>
<ol style="list-style: none; margin: 0; padding: 0; border: 1px solid #E6E8EC; border-radius: 8px; overflow: hidden">
<sc-for list="{{ m.conv }}" as="c" hint-placeholder-count="4">
<li style="border-top: {{ c.borderTop }}">
<sc-if value="{{ c.isCurrent }}" hint-placeholder-val="{{ false }}">
<div aria-current="true" style="display: flex; align-items: center; gap: 10px; min-height: 48px; box-sizing: border-box; padding: 6px 14px 6px 40px; background: #F2F8F8">
<span aria-hidden="true" style="flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 50%; background: #E3F2F3; color: #0A5F67; font-size: 12px; font-weight: 600">{{ c.initial }}</span>
<span style="flex: 0 0 auto; font-weight: 600">{{ c.who }}</span>
<span style="flex: 1 1 auto; min-width: 0; font-size: 12.5px; color: #0A5F67; font-weight: 600">지금 보고 있는 메일</span>
<span style="flex: 0 0 auto; font-size: 12px; color: #667085; font-variant-numeric: tabular-nums">{{ c.date }}</span>
</div>
</sc-if>
<sc-if value="{{ c.isOther }}" hint-placeholder-val="{{ true }}">
<button type="button" class="row" aria-expanded="{{ c.expandedStr }}" onClick="{{ c.toggle }}" style="width: 100%; display: flex; align-items: center; gap: 10px; min-height: 48px; box-sizing: border-box; padding: 6px 14px; border: 0; background: #FFFFFF; font: inherit; color: inherit; text-align: left; cursor: pointer">
<sc-if value="{{ c.expanded }}" hint-placeholder-val="{{ false }}"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#667085" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="flex: 0 0 auto"><polyline points="6 9 12 15 18 9"></polyline></svg></sc-if>
<sc-if value="{{ c.collapsed }}" hint-placeholder-val="{{ true }}"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#667085" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="flex: 0 0 auto"><polyline points="9 18 15 12 9 6"></polyline></svg></sc-if>
<span aria-hidden="true" style="flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 50%; background: {{ c.avatarBg }}; color: {{ c.avatarFg }}; font-size: 12px; font-weight: 600">{{ c.initial }}</span>
<span style="flex: 0 0 auto; font-weight: 600">{{ c.who }}</span>
<sc-if value="{{ c.isSent }}" hint-placeholder-val="{{ false }}"><span style="flex: 0 0 auto; font-size: 12px; color: #667085">보낸편지함</span></sc-if>
<span style="flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12.5px; color: #667085">{{ c.preview }}</span>
<span style="flex: 0 0 auto; font-size: 12px; color: #667085; font-variant-numeric: tabular-nums">{{ c.date }}</span>
</button>
<sc-if value="{{ c.expanded }}" hint-placeholder-val="{{ false }}">
<div style="display: flex; flex-direction: column; gap: 10px; padding: 2px 16px 14px 78px">
<div style="white-space: pre-line; font-size: 13.5px; line-height: 1.7; color: #1F2733">{{ c.body }}</div>
<div style="display: flex; flex-wrap: wrap; gap: 4px; margin-left: -10px">
<a href="Compose.dc.html" style="display: inline-flex; align-items: center; min-height: 36px; padding: 0 10px; border-radius: 8px; color: #0E7C86; font-weight: 600; text-decoration: none">답장</a>
<a href="#" style="display: inline-flex; align-items: center; min-height: 36px; padding: 0 10px; border-radius: 8px; color: #0E7C86; font-weight: 600; text-decoration: none">이 메일만 열기</a>
</div>
</div>
</sc-if>
</sc-if>
</li>
</sc-for>
</ol>
</section>
</sc-if>
</div>
</article>
</div>
</main>
</div>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props='{"variant":{"editor":"enum","options":["묶음 켬","묶음 끔"],"default":"묶음 켬"},"$preview":{"width":1440,"height":900}}'>
class Component extends DCLogic {
state = { grouped: null, sel: 'c1', open: {} };
convs() {
return [
{
id: 'c1', who: '이도윤, 나', count: 4, time: '오전 10:42', subject: 'Re: 유지보수 계약 갱신 건', unread: true, att: true,
fromName: '이도윤', fromEmail: 'doyun.lee@arasystem.it.kr', date: '2026. 11. 5. (목) 오전 10:42',
body: '김아라님, 안녕하세요.\n\n말씀하신 조건으로 계약서 초안을 고쳐 첨부했습니다.\n기간은 1년이고, 단가는 작년과 같습니다.\n\n이도윤 드림',
conv: [
{ id: 'a1', who: '나', initial: '김', sent: true, date: '10월 27일 (화)', preview: '다음 달에 끝나는 유지보수 계약을 1년 더 이어 가려고 합니다.', body: '이도윤님,\n\n다음 달에 끝나는 유지보수 계약을 1년 더 이어 가려고 합니다.\n조건을 정리해서 보내 주시면 검토하겠습니다.\n\n김아라' },
{ id: 'a2', who: '이도윤', initial: '이', date: '10월 29일 (목)', preview: '네, 확인했습니다. 단가와 기간을 정리해서 다음 주 초에 보내 드리겠습니다.', body: '네, 확인했습니다.\n단가와 기간을 정리해서 다음 주 초에 보내 드리겠습니다.\n\n이도윤' },
{ id: 'a3', who: '나', initial: '김', sent: true, date: '10월 29일 (목)', preview: '감사합니다. 초안은 다음 주 초까지 받으면 됩니다.', body: '감사합니다.\n초안은 다음 주 초까지 받으면 됩니다.\n\n김아라' },
{ id: 'a4', who: '이도윤', initial: '이', current: true, date: '오늘 오전 10:42' },
],
},
{
id: 'c2', who: '인사팀', count: 1, time: '오전 9:05', subject: '[공지] 계약직 채용 일정 안내', unread: true,
fromName: '인사팀', fromEmail: 'hr@arasystem.it.kr', date: '2026. 11. 5. (목) 오전 9:05',
body: '구성원 여러분, 인사팀입니다.\n\n11월 계약직 채용은 아래 일정으로 진행합니다.\n서류는 11월 12일(목)까지 받습니다.', conv: [],
},
{
id: 'c3', who: '정하늘, 나', count: 3, time: '어제', subject: 'Re: 신규 입사자 교육 회의록',
fromName: '정하늘', fromEmail: 'haneul.jung@arasystem.it.kr', date: '2026. 11. 4. (수) 오후 3:12',
body: '아라님,\n\n고친 회의록을 다시 보내 드립니다.\n교육은 다음 주 화요일 오전으로 정했습니다.\n\n정하늘',
conv: [
{ id: 'b1', who: '정하늘', initial: '정', date: '10월 28일 (수)', preview: '신규 입사자 교육 회의록을 공유합니다.', body: '아라님,\n\n신규 입사자 교육 회의록을 공유합니다.\n\n정하늘' },
{ id: 'b2', who: '나', initial: '김', sent: true, date: '11월 2일 (월)', preview: '교육 날짜만 한 번 더 확인해 주실 수 있을까요?', body: '하늘님,\n\n교육 날짜만 한 번 더 확인해 주실 수 있을까요?\n\n김아라' },
{ id: 'b3', who: '정하늘', initial: '정', current: true, date: '어제 오후 3:12' },
],
},
{
id: 'c4', who: '박준호', count: 1, time: '어제', subject: '11월 인프라 점검 일정 조율',
fromName: '박준호', fromEmail: 'junho.park@arasystem.it.kr', date: '2026. 11. 4. (수) 오전 9:05',
body: '인프라팀 여러분,\n\n11월 점검 일정을 정하려고 합니다.\n안 되시는 날을 알려 주세요.\n\n박준호', conv: [],
},
{
id: 'c5', who: '최서연', count: 1, time: '10월 30일', subject: '11월 비용 정산 안내',
fromName: '최서연', fromEmail: 'seoyeon.choi@arasystem.it.kr', date: '2026. 10. 30. (금) 오후 4:20',
body: '안녕하세요, 재무팀 최서연입니다.\n\n10월분 비용 마감은 11월 4일(수)입니다.', conv: [],
},
{
id: 'c6', who: '인사팀, 김민수, 나', count: 6, time: '10월 29일', subject: 'Re: 전사 회의 참석 안내', att: true,
fromName: '김민수', fromEmail: 'minsu.kim@arasystem.it.kr', date: '2026. 10. 29. (목) 오후 6:02',
body: '모두 고맙습니다.\n자리 배치는 첨부한 표대로 하겠습니다.\n\n김민수',
conv: [
{ id: 'd1', who: '인사팀', initial: '인', date: '10월 19일 (월)', preview: '10월 30일 오후 4시 전사 회의에 참석해 주세요.', body: '구성원 여러분, 인사팀입니다.\n\n10월 30일(금) 오후 4시 전사 회의에 참석해 주세요.' },
{ id: 'd2', who: '나', initial: '김', sent: true, date: '10월 20일 (화)', preview: '운영팀은 모두 참석합니다.', body: '운영팀은 모두 참석합니다.\n\n김아라' },
{ id: 'd3', who: '김민수', initial: '김', current: true, date: '10월 29일 오후 6:02' },
],
},
];
}
messages() {
return [
{ id: 'c1', who: '이도윤', time: '오전 10:42', subject: 'Re: 유지보수 계약 갱신 건', unread: true, att: true },
{ id: 'c2', who: '인사팀', time: '오전 9:05', subject: '[공지] 계약직 채용 일정 안내', unread: true },
{ id: 'c3', who: '정하늘', time: '어제', subject: 'Re: 신규 입사자 교육 회의록' },
{ id: 'c4', who: '박준호', time: '어제', subject: '11월 인프라 점검 일정 조율' },
{ id: 'c5', who: '최서연', time: '10월 30일', subject: '11월 비용 정산 안내' },
{ id: 'c1', who: '이도윤', time: '10월 29일', subject: '회신: 유지보수 계약 갱신 건', key: 'm6' },
{ id: 'c6', who: '김민수', time: '10월 29일', subject: 'Re: 전사 회의 참석 안내', att: true },
{ id: 'c3', who: '정하늘', time: '10월 28일', subject: '신규 입사자 교육 회의록', key: 'm8' },
{ id: 'c6', who: '인사팀', time: '10월 19일', subject: '전사 회의 참석 안내', key: 'm9' },
];
}
renderVals() {
const st = this.state;
const variant = this.props.variant ?? '묶음 켬';
const grouped = st.grouped ?? (variant === '묶음 켬');
const convs = this.convs();
const pickConv = (id) => this.setState({ sel: id, open: {} });
const rows = grouped
? convs.map((c) => ({
who: c.who,
time: c.time,
subject: c.subject,
hasAtt: !!c.att,
hasCount: c.count > 1,
countText: c.count + '통',
bg: c.id === st.sel ? '#E3F2F3' : '#FFFFFF',
fw: c.unread ? '650' : '450',
subFw: c.unread ? '600' : '400',
timeFw: c.unread ? '600' : '400',
timeColor: c.unread ? '#171C26' : '#667085',
pick: () => pickConv(c.id),
}))
: this.messages().map((x) => ({
who: x.who,
time: x.time,
subject: x.subject,
hasAtt: !!x.att,
hasCount: false,
countText: '',
bg: x.id === st.sel && !x.key ? '#E3F2F3' : '#FFFFFF',
fw: x.unread ? '650' : '450',
subFw: x.unread ? '600' : '400',
timeFw: x.unread ? '600' : '400',
timeColor: x.unread ? '#171C26' : '#667085',
pick: () => pickConv(x.id),
}));
const cur = convs.find((c) => c.id === st.sel) || convs[0];
const conv = cur.conv.map((c, i) => {
const expanded = !!st.open[c.id];
return {
...c,
isCurrent: !!c.current,
isOther: !c.current,
isSent: !!c.sent,
expanded: expanded,
collapsed: !expanded,
expandedStr: expanded ? 'true' : 'false',
borderTop: i === 0 ? '0' : '1px solid #EEF0F3',
avatarBg: c.sent ? '#F2F4F7' : '#E3F2F3',
avatarFg: c.sent ? '#4A5363' : '#0A5F67',
toggle: () => this.setState({ open: { ...st.open, [c.id]: !expanded } }),
};
});
return {
rows: rows,
headCount: grouped ? '대화 6개, 안 읽은 메일 2통' : '메일 9통, 안 읽은 메일 2통',
listLabel: grouped ? '받은편지함 대화' : '받은편지함 메일',
groupedStr: grouped ? 'true' : 'false',
groupBg: grouped ? '#E3F2F3' : '#FFFFFF',
groupFg: grouped ? '#0A5F67' : '#344054',
groupBorder: grouped ? '#9FD3D7' : '#D0D5DD',
toggleGroup: () => this.setState({ grouped: !grouped }),
m: {
subject: cur.subject,
fromName: cur.fromName,
fromEmail: cur.fromEmail,
date: cur.date,
body: cur.body,
initial: cur.fromName.slice(0, 1),
hasConv: conv.length > 1,
convCount: conv.length,
conv: conv,
},
};
}
}
</script>
</body>
</html>
+46
View File
@@ -1028,6 +1028,52 @@ API 와 서버 규칙은 계약 2절 "실시간 알림".
비밀번호 스스로 재설정(복구 연락처가 필요하다), 새 기기 로그인 알림 메일, 잠긴 계정만의 안내 글, 공용 주소로 보내기 · 공유 메일함.
시안에서 뺀 것: 알림 메일의 링크 단추(넣지 않는다 — 위 규칙), 유예 노란 줄의 닫기, 맥 메일 · 휴대폰 Outlook 앱의 연결 차례, 프로그램별 화면 그림.
## 14단계 — 검색 · 대화 묶기 (2026-11-05 확정 — `Search14` · `Thread14` · `ListThreads14`)
자체 검색 색인으로 붙여 쓴 말 · 앞부분 · 제목 맨 앞의 말머리까지 찾고(부분 일치), 조건 검색과 검색어 강조를 더한다.
같은 대화(답장 · 전달로 이어진 메일)를 읽기 화면에 늘 보이고, 목록은 사용자가 켜면 대화로 묶는다.
까닭은 엔진 실측(2026-11-03)이다 — 엔진 스레드는 `회신:` · `답장:` · `전달:` 을 떼지 않아 한국어 Outlook 의 답장이 따로 떨어지고, 엔진 검색은 어절이 통째로 같을 때만 맞는다.
데이터 · API 규칙(색인 · 대화 · 조건)은 구현 때 계약 2절에 더한다. 구현 · 배포는 전환 뒤 안정화(1~2주) 다음이다.
새 토큰은 없다 — 조건 칸 40 은 필터 조건 칸의 밀도 예외(`--size-filter-control`), [대화로 묶기] 36 은 안내 줄 단추의 밀도 예외(`--size-notice-action`)를 다시 쓴다.
문구는 위 「화면 문구 — 사용자에게 말하듯」을 따른다.
### 검색 · 조건 검색 · 강조 (`Search14`)
- **찾는 방식**: 서버의 자체 색인(지금 DB — 글자 두 개씩 묶은 색인)에서 부분 일치로 찾는다. "계약"으로 계약서 · 계약직 · 계약은 · 계약을, 제목 맨 앞의 [공지] · Re: 까지 찾는다.
여러 낱말은 모두 맞아야 하고 큰따옴표는 구절이다(지금과 같다). 영문은 대소문자를 가리지 않는다. 끝이 바뀐 말('보내' → '보냈습니다')과 첨부 파일 안의 글은 찾지 않는다(첨부 이름은 찾는다).
- **[조건]**: 검색 칸 안 오른쪽(✕ 앞)의 단추. 누르면 칸 바로 아래에 「조건으로 찾기」 패널(검색 칸 폭)이 열린다(aria-expanded).
- 칸: 보낸 사람 · 받는 사람(참조도) · 제목 · 내용 · 기간(전체 기간 · 최근 1주 · 최근 1달 · 최근 3달 · 최근 1년 · 날짜 고르기 → 시작 ~ 끝) · 첨부(체크 "첨부 파일이 있는 메일만"). 칸 높이 40.
- 아래 안내 "검색 칸의 말과 이 조건에 모두 맞는 메일을 찾습니다. 받는 사람에는 참조로 받은 사람도 들어갑니다." 단추 [조건 지우기] · [찾기].
- 패널이 열려 있거나 조건이 걸려 있으면 [조건] 단추가 청록 바탕이다. 검색 칸이 비어도 조건만으로 찾을 수 있다.
- **조건이 걸린 결과**: 찾을 곳 줄 아래 한 줄 "조건: 이도윤 님이 보낸 메일, 최근 1달, 첨부 있음" + [조건 지우기]. 조건은 쉼표로 잇는다(꼬리표 · 알약을 쓰지 않는다).
- **찾을 곳**은 지금과 같다(그 메일함 | 전체 메일함 — 전체는 휴지통과 스팸메일함을 뺀다). 조건도 주소에 싣는다 — 이름은 구현 때 계약에서 정하고, 검색어처럼 로그에 남기지 않는다.
- **결과 줄**(높이 64, 세 줄): 보낸 사람 · 시각 / 제목(+ 첨부 표시) / 미리 보기 한 줄(맞은 말 둘레). 제목 · 미리 보기의 맞은 말은 `<mark>`(노랑 `#FCE7A4` · 글자색은 그대로).
- **읽기 화면 강조**: 제목 · 본문 · 첨부 이름의 맞은 말. 본문은 서버가 정화한 HTML 의 글 조각에만 `<mark>` 를 넣는다(찾기와 같은 정규화 · 부분 일치). 맞은 곳 사이 이동(다음 · 이전)은 이번에 넣지 않는다.
- **검색 준비 중**: 지난 메일의 색인을 만드는 동안 그 직원의 검색은 지금 방식(엔진) 그대로이고, 결과 머리 아래 안내 줄(옅은 청록 바탕 · role=status)
"지난 메일을 검색할 수 있게 준비하고 있습니다. 준비가 끝날 때까지는 낱말이 통째로 같은 메일만 찾습니다."
- **결과 없음**: "‘계약’에 맞는 메일이 없습니다" / "다른 말로 찾거나 조건을 줄여 보세요." + [전체 메일함에서 찾기](그 메일함에서 찾을 때만). 지금의 한계 안내("낱말 단위로 찾습니다 …")는 색인이 준비된 뒤에는 뺀다.
- 검색 결과는 대화로 묶지 않는다(한 통씩).
### 읽기 화면 — 같은 대화 (`Thread14`)
- 본문 · 첨부 아래 "같은 대화 N통". 오래된 차례로 테두리 상자 안의 줄(높이 48): ▸ · 머리글자 · 이름(내가 보냈으면 "나" + 회색 "보낸편지함") · 미리 보기 한 줄 · 시각.
- 지금 보는 메일의 줄은 옅은 청록 바탕에 "지금 보고 있는 메일"이고 누를 수 없다. 다른 줄을 누르면 그 자리에서 펼쳐 본문과 [답장] · [이 메일만 열기]가 보이고(aria-expanded), 다시 누르면 접는다.
- 대화에 드는 메일: 보낸편지함을 포함한 모든 메일함 — 휴지통과 스팸메일함은 뺀다. 한 통뿐이면 이 칸이 없다.
- **같은 대화의 기준**(우리 규칙 — 엔진보다 넓다): 답장 관계(References · In-Reply-To)가 있고 말머리를 뗀 제목이 같을 때.
떼는 말머리는 Re · RE · Fw · FW · Fwd · AW · WG · **회신 · 답장 · 전달** · 대괄호 꼬리표이고 여러 겹도 뗀다. 제목이 다르면 묶지 않는다(엔진과 같다 — 남의 메일이 잘못 붙지 않게).
원본보다 늦게 색인된 메일은 들어온 뒤 합친다. 색인을 다 만들기 전 그 직원은 엔진의 스레드 그대로다.
### 목록 — 대화로 묶어 보기 (`ListThreads14`)
- 목록 머리 오른쪽의 [대화로 묶기](눌림 단추 — aria-pressed, 높이 36)와 설정 › 메일 › 보내기 · 읽기의 같은 켬/끔 한 줄. 사용자마다 서버에 저장한다(다른 기기에서도 같다). **기본은 끔**이다 — 전환 직후라 보던 목록을 바꾸지 않는다.
- 켜면 한 줄이 그 메일함의 대화 하나다: 참여자(이름을 쉼표로 — 내가 보냈으면 "나") · 통 수("4통" — 회색 글, 뱃지 · 알약 없음, 한 통이면 없음) · 마지막 시각 / 마지막 메일 제목(+ 첨부 표시). 하나라도 안 읽었으면 굵게.
- 통 수는 대화 전체(보낸편지함 포함 · 휴지통과 스팸 빼고)이고, 고르기 · 읽음 · 중요 · 이동 · 삭제는 그 메일함 안에 있는 그 대화의 메일 모두에 한다.
- 줄을 누르면 그 메일함의 마지막 메일을 열고, 아래에 같은 대화 칸이 보인다. 목록 머리 글은 켰을 때 "대화 6개, 안 읽은 메일 2통", 껐을 때 "메일 9통, 안 읽은 메일 2통" 꼴이다.
### 처음 판에서 미룬 것
본문 강조의 다음 · 이전 이동, 검색어 줄임말(from: 같은 꼴), 대화째 끌어다 놓기, 첨부 파일 안의 글 찾기, 끝이 바뀐 말 찾기(형태소 분석).
## 구현하며 정한 보완 (2026-10-26 — 캔버스에는 아직 없다)
- 찾지 못한 첨부(보관 시간이 지난 업로드·지워진 원본)는 실패로 표시하고, 전달 원본에는 "빼기"만 둔다(다시 올릴 수 없다).
+458
View File
@@ -0,0 +1,458 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<title>메일 검색 · 조건 검색 · 강조</title>
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<style>
@font-face{font-family:'Pretendard';src:url('/_blob/16067b9aa5137abf808e3dbef30c42e0') format('woff2-variations'),url('/_blob/16067b9aa5137abf808e3dbef30c42e0') format('woff2');font-weight:45 920;font-style:normal;font-display:swap}
body{margin:0}
a{color:#0E7C86}a:hover{color:#0A5F67}
:focus-visible{outline:2px solid #0E7C86;outline-offset:2px}
.seg{background:transparent;color:#4A5363}
.seg[aria-pressed="true"]{background:#FFFFFF;color:#171C26;box-shadow:0 1px 2px rgba(16,24,28,.14)}
.sk{display:block;height:10px;border-radius:5px;background:#EEF0F3}
mark{background:#FCE7A4;color:inherit;border-radius:2px;padding:0 1px}
.fi{box-sizing:border-box;height:40px;padding:0 10px;border:1px solid #D0D5DD;border-radius:8px;background:#FFFFFF;font:inherit;color:#171C26}
.fi:focus{outline:none;border-color:#0E7C86;box-shadow:0 0 0 1px #0E7C86}
</style>
</helmet>
<div style="min-height: 100vh; display: flex; background: #FFFFFF; color: #171C26; font-family: Pretendard, -apple-system, 'Apple SD Gothic Neo', 'Malgun Gothic', system-ui, sans-serif; font-size: 13px; line-height: 1.5">
<nav aria-label="사내 시스템" style="flex: 0 0 72px; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; gap: 14px; padding: 10px 0 12px; background: #0B3640">
<a href="Main.dc.html" style="display: inline-flex; align-items: center; justify-content: center; width: 60px; min-height: 44px; border-radius: 10px; text-decoration: none">
<img src="/_blob/2b9ef4349333debc969432c859eaf1bf" alt="ARA SYSTEM" width="48" height="17" style="display: block; width: 48px; height: auto">
</a>
<ul style="list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px">
<li>
<a href="Main.dc.html" aria-current="page" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; width: 60px; min-height: 56px; border-radius: 10px; background: #155A64; color: #FFFFFF; font-size: 11px; font-weight: 600; text-decoration: none">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
<span>메일</span>
</a>
</li>
<li>
<a href="#" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; width: 60px; min-height: 56px; border-radius: 10px; color: #A3C2C6; font-size: 11px; font-weight: 500; text-decoration: none">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
<span>캘린더</span>
</a>
</li>
<li>
<a href="#" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; width: 60px; min-height: 56px; border-radius: 10px; color: #A3C2C6; font-size: 11px; font-weight: 500; text-decoration: none">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
<span>파일</span>
</a>
</li>
<li>
<a href="#" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; width: 60px; min-height: 56px; border-radius: 10px; color: #A3C2C6; font-size: 11px; font-weight: 500; text-decoration: none">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
<span>주소록</span>
</a>
</li>
</ul>
<a href="Settings.dc.html" style="margin-top: auto; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; width: 60px; min-height: 56px; border-radius: 10px; color: #A3C2C6; font-size: 11px; font-weight: 500; text-decoration: none">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
<span>설정</span>
</a>
</nav>
<div style="flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column">
<header style="position: relative; z-index: 5; display: flex; align-items: center; flex-wrap: wrap; gap: 8px 16px; box-sizing: border-box; min-height: 56px; padding: 6px 12px 6px 20px; border-bottom: 1px solid #E6E8EC">
<h1 style="margin: 0; font-size: 16px; font-weight: 700; letter-spacing: -0.01em">메일</h1>
<form role="search" aria-label="메일 검색" style="position: relative; flex: 1 1 320px; max-width: 560px; display: flex; align-items: center; gap: 6px; box-sizing: border-box; height: 40px; margin: 0; padding: 0 0 0 12px; border: 1px solid #0E7C86; border-radius: 8px; background: #FFFFFF; box-shadow: 0 0 0 1px #0E7C86">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#667085" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
<input type="search" aria-label="메일 검색" placeholder="메일 검색" value="계약" style="flex: 1 1 auto; min-width: 0; border: 0; outline: none; background: transparent; font: inherit; color: #171C26">
<button type="button" onClick="{{ toggleCond }}" aria-expanded="{{ condOpenStr }}" aria-controls="search-conditions" style="flex: 0 0 auto; display: inline-flex; align-items: center; gap: 5px; height: 32px; padding: 0 9px; border: 0; border-radius: 6px; background: {{ condBtnBg }}; color: {{ condBtnFg }}; font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="4" y1="21" x2="4" y2="14"></line><line x1="4" y1="10" x2="4" y2="3"></line><line x1="12" y1="21" x2="12" y2="12"></line><line x1="12" y1="8" x2="12" y2="3"></line><line x1="20" y1="21" x2="20" y2="16"></line><line x1="20" y1="12" x2="20" y2="3"></line><line x1="1" y1="14" x2="7" y2="14"></line><line x1="9" y1="8" x2="15" y2="8"></line><line x1="17" y1="16" x2="23" y2="16"></line></svg>
<span>조건</span>
</button>
<a href="Main.dc.html" aria-label="검색어 지우기" title="검색어 지우기 (Esc)" style="flex: 0 0 auto; width: 40px; height: 38px; display: inline-flex; align-items: center; justify-content: center; border-radius: 0 7px 7px 0; color: #667085; text-decoration: none">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
</a>
<sc-if value="{{ condOpen }}" hint-placeholder-val="{{ false }}">
<div id="search-conditions" role="group" aria-labelledby="cond-title" style="position: absolute; top: 46px; left: -2px; right: -2px; z-index: 20; box-sizing: border-box; display: flex; flex-direction: column; gap: 14px; padding: 16px 18px 14px; border: 1px solid #D0D5DD; border-radius: 10px; background: #FFFFFF; box-shadow: 0 12px 32px rgba(16, 24, 40, 0.16)">
<h2 id="cond-title" style="margin: 0; font-size: 13.5px; font-weight: 700">조건으로 찾기</h2>
<div style="display: grid; grid-template-columns: 76px minmax(0, 1fr); gap: 10px 12px; align-items: center">
<label for="c-from" style="font-size: 12.5px; font-weight: 600; color: #4A5363">보낸 사람</label>
<input id="c-from" class="fi" type="text" value="이도윤" placeholder="이름이나 메일 주소">
<label for="c-to" style="font-size: 12.5px; font-weight: 600; color: #4A5363">받는 사람</label>
<input id="c-to" class="fi" type="text" placeholder="이름이나 메일 주소">
<label for="c-subject" style="font-size: 12.5px; font-weight: 600; color: #4A5363">제목</label>
<input id="c-subject" class="fi" type="text" placeholder="제목에 든 말">
<label for="c-body" style="font-size: 12.5px; font-weight: 600; color: #4A5363">내용</label>
<input id="c-body" class="fi" type="text" placeholder="본문에 든 말">
<label for="c-period" style="font-size: 12.5px; font-weight: 600; color: #4A5363">기간</label>
<div style="display: flex; flex-wrap: wrap; align-items: center; gap: 8px">
<select id="c-period" class="fi" value="{{ period }}" onChange="{{ setPeriod }}" style="min-width: 140px; cursor: pointer">
<sc-for list="{{ periods }}" as="p" hint-placeholder-count="6"><option value="{{ p.v }}">{{ p.t }}</option></sc-for>
</select>
<sc-if value="{{ isCustom }}" hint-placeholder-val="{{ false }}">
<span style="display: inline-flex; align-items: center; gap: 6px">
<input type="date" class="fi" aria-label="시작 날짜" value="2026-10-01">
<span aria-hidden="true" style="color: #667085">~</span>
<input type="date" class="fi" aria-label="끝 날짜" value="2026-11-05">
</span>
</sc-if>
</div>
<span style="font-size: 12.5px; font-weight: 600; color: #4A5363">첨부</span>
<label style="display: inline-flex; align-items: center; gap: 8px; min-height: 40px; cursor: pointer">
<input type="checkbox" checked="{{ attOnly }}" onChange="{{ toggleAtt }}" style="width: 18px; height: 18px; margin: 0; accent-color: #0E7C86">
<span>첨부 파일이 있는 메일만</span>
</label>
</div>
<p style="margin: 0; font-size: 12px; color: #667085">검색 칸의 말과 이 조건에 모두 맞는 메일을 찾습니다. 받는 사람에는 참조로 받은 사람도 들어갑니다.</p>
<div style="display: flex; justify-content: flex-end; gap: 8px">
<button type="button" onClick="{{ clearCond }}" style="min-height: 40px; padding: 0 14px; border: 1px solid #D0D5DD; border-radius: 8px; background: #FFFFFF; color: #344054; font: inherit; font-weight: 600; cursor: pointer">조건 지우기</button>
<button type="button" onClick="{{ applyCond }}" style="min-height: 40px; padding: 0 18px; border: 0; border-radius: 8px; background: #0E7C86; color: #FFFFFF; font: inherit; font-weight: 600; cursor: pointer">찾기</button>
</div>
</div>
</sc-if>
</form>
<div style="margin-left: auto; display: flex; align-items: center; gap: 10px">
<span aria-hidden="true" style="display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 50%; background: #E3F2F3; color: #0A5F67; font-size: 13px; font-weight: 600">김</span>
<div style="display: flex; flex-direction: column; line-height: 1.3">
<span style="font-weight: 600">김아라</span>
<span style="font-size: 12px; color: #667085">ara.kim@arasystem.it.kr</span>
</div>
<a href="Login.dc.html" style="display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 10px; border-radius: 8px; color: #4A5363; font-weight: 500; text-decoration: none">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>
<span>로그아웃</span>
</a>
</div>
</header>
<div style="flex: 1 1 auto; display: flex; flex-wrap: wrap; align-items: stretch">
<nav aria-label="메일함" style="flex: 1 1 208px; box-sizing: border-box; display: flex; flex-direction: column; gap: 14px; padding: 14px 10px; background: #F8F9FB; border-right: 1px solid #E6E8EC">
<a href="Compose.dc.html" style="display: flex; align-items: center; justify-content: center; gap: 8px; min-height: 44px; border-radius: 8px; background: #0E7C86; color: #FFFFFF; font-size: 13.5px; font-weight: 600; text-decoration: none">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path></svg>
<span>메일 쓰기</span>
</a>
<ul style="list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 1px">
<sc-for list="{{ boxes }}" as="b" hint-placeholder-count="5">
<li>
<a href="Main.dc.html" aria-current="{{ b.current }}" style="display: flex; align-items: center; gap: 10px; min-height: 40px; box-sizing: border-box; padding: 0 10px; border-radius: 7px; background: {{ b.bg }}; color: {{ b.fg }}; font-weight: {{ b.fw }}; text-decoration: none">
<span style="flex: 1 1 auto">{{ b.name }}</span>
<sc-if value="{{ b.hasUnread }}" hint-placeholder-val="{{ false }}"><span style="font-size: 12px; font-weight: 600; color: {{ b.countColor }}; font-variant-numeric: tabular-nums">{{ b.unread }}</span></sc-if>
</a>
</li>
</sc-for>
</ul>
</nav>
<main style="flex: 999 1 560px; min-width: 0; display: flex; flex-wrap: wrap; align-items: stretch">
<section aria-labelledby="result-title" style="flex: 1 1 384px; min-width: 0; box-sizing: border-box; display: flex; flex-direction: column; border-right: 1px solid #E6E8EC">
<div style="display: flex; align-items: center; gap: 6px; min-height: 52px; box-sizing: border-box; padding: 4px; border-bottom: 1px solid #E6E8EC">
<a href="Main.dc.html" aria-label="검색 닫기" title="검색을 닫고 받은편지함으로" style="flex: 0 0 auto; width: 40px; height: 44px; display: inline-flex; align-items: center; justify-content: center; border-radius: 8px; color: #4A5363; text-decoration: none">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><line x1="19" y1="12" x2="5" y2="12"></line><polyline points="12 19 5 12 12 5"></polyline></svg>
</a>
<h2 id="result-title" style="margin: 0; font-size: 15px; font-weight: 700; letter-spacing: -0.01em">검색 결과</h2>
<span style="min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; color: #667085; font-variant-numeric: tabular-nums">{{ countText }}</span>
</div>
<div style="display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; min-height: 48px; box-sizing: border-box; padding: 4px 12px 4px 16px; border-bottom: 1px solid #EEF0F3; background: #FBFBFC">
<span id="scope-label" style="font-size: 12.5px; font-weight: 600; color: #4A5363">찾을 곳</span>
<div role="group" aria-labelledby="scope-label" style="display: inline-flex; gap: 2px; padding: 2px; border-radius: 8px; background: #F2F4F7">
<button type="button" class="seg" aria-pressed="{{ boxPressed }}" onClick="{{ scopeBox }}" style="min-height: 36px; padding: 0 12px; border: 0; border-radius: 6px; font: inherit; font-weight: 600; cursor: pointer">받은편지함</button>
<button type="button" class="seg" aria-pressed="{{ allPressed }}" onClick="{{ scopeAll }}" style="min-height: 36px; padding: 0 12px; border: 0; border-radius: 6px; font: inherit; font-weight: 600; cursor: pointer">전체 메일함</button>
</div>
<sc-if value="{{ isAll }}" hint-placeholder-val="{{ false }}"><span style="font-size: 12px; color: #667085">휴지통과 스팸메일함은 빼고 찾습니다.</span></sc-if>
</div>
<sc-if value="{{ applied }}" hint-placeholder-val="{{ false }}">
<div style="display: flex; flex-wrap: wrap; align-items: center; gap: 2px 8px; min-height: 40px; box-sizing: border-box; padding: 2px 8px 2px 16px; border-bottom: 1px solid #EEF0F3">
<span style="font-size: 12.5px; color: #344054">{{ condSummary }}</span>
<button type="button" onClick="{{ clearCond }}" style="min-height: 36px; padding: 0 8px; border: 0; border-radius: 6px; background: transparent; color: #0E7C86; font: inherit; font-size: 12.5px; font-weight: 600; cursor: pointer">조건 지우기</button>
</div>
</sc-if>
<sc-if value="{{ showIndexing }}" hint-placeholder-val="{{ false }}">
<div role="status" style="display: flex; align-items: flex-start; gap: 8px; box-sizing: border-box; padding: 10px 16px; border-bottom: 1px solid #E3EEEF; background: #F2F8F8; font-size: 12.5px; color: #344054">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#0E7C86" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="flex: 0 0 auto; margin-top: 2px"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="16" x2="12" y2="12"></line><line x1="12" y1="8" x2="12.01" y2="8"></line></svg>
<span>지난 메일을 검색할 수 있게 준비하고 있습니다. 준비가 끝날 때까지는 낱말이 통째로 같은 메일만 찾습니다.</span>
</div>
</sc-if>
<sc-if value="{{ showList }}" hint-placeholder-val="{{ true }}">
<ul aria-label="검색 결과" style="list-style: none; margin: 0; padding: 0">
<sc-for list="{{ items }}" as="item" hint-placeholder-count="5">
<li style="border-bottom: 1px solid #EEF0F3; background: {{ item.bg }}">
<button type="button" onClick="{{ item.pick }}" style="width: 100%; min-height: 64px; box-sizing: border-box; display: flex; flex-direction: column; justify-content: center; gap: 1px; padding: 7px 16px; border: 0; background: transparent; font: inherit; line-height: 1.4; color: inherit; text-align: left; cursor: pointer">
<span style="display: flex; align-items: center; gap: 6px">
<span style="flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: {{ item.fw }}; color: {{ item.whoColor }}">{{ item.who }}</span>
<sc-if value="{{ item.showBox }}" hint-placeholder-val="{{ false }}"><span style="flex: 0 0 auto; font-size: 12px; color: #667085">{{ item.boxName }}</span></sc-if>
<span style="flex: 0 0 auto; font-size: 12px; font-weight: {{ item.timeFw }}; color: {{ item.timeColor }}; font-variant-numeric: tabular-nums">{{ item.time }}</span>
</span>
<span style="display: flex; align-items: center; gap: 6px; min-width: 0">
<span style="min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: {{ item.subFw }}; color: #171C26"><sc-for list="{{ item.subParts }}" as="p" hint-placeholder-count="1"><sc-if value="{{ p.hit }}" hint-placeholder-val="{{ false }}"><mark>{{ p.t }}</mark></sc-if><sc-if value="{{ p.plain }}" hint-placeholder-val="{{ true }}"><span>{{ p.t }}</span></sc-if></sc-for></span>
<sc-if value="{{ item.hasAtt }}" hint-placeholder-val="{{ false }}"><svg role="img" aria-label="첨부 있음" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#667085" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex: 0 0 auto"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg></sc-if>
</span>
<span style="min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12.5px; color: #667085"><sc-for list="{{ item.preParts }}" as="p" hint-placeholder-count="1"><sc-if value="{{ p.hit }}" hint-placeholder-val="{{ false }}"><mark>{{ p.t }}</mark></sc-if><sc-if value="{{ p.plain }}" hint-placeholder-val="{{ true }}"><span>{{ p.t }}</span></sc-if></sc-for></span>
</button>
</li>
</sc-for>
</ul>
</sc-if>
<sc-if value="{{ showEmpty }}" hint-placeholder-val="{{ false }}">
<div style="display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 72px 28px; text-align: center">
<svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="#98A2B3" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
<p style="margin: 6px 0 0; font-size: 14px; font-weight: 600">‘계약’에 맞는 메일이 없습니다</p>
<p style="margin: 0; max-width: 300px; font-size: 12.5px; color: #667085">다른 말로 찾거나 조건을 줄여 보세요.</p>
<sc-if value="{{ offerAll }}" hint-placeholder-val="{{ true }}">
<button type="button" onClick="{{ scopeAll }}" style="margin-top: 8px; min-height: 44px; padding: 0 16px; border: 0; border-radius: 8px; background: transparent; color: #0E7C86; font: inherit; font-weight: 600; cursor: pointer">전체 메일함에서 찾기</button>
</sc-if>
</div>
</sc-if>
</section>
<div style="flex: 999 1 480px; min-width: 0; box-sizing: border-box; display: flex; flex-direction: column">
<sc-if value="{{ hasSel }}" hint-placeholder-val="{{ true }}">
<article aria-labelledby="mail-subject" style="display: flex; flex-direction: column">
<div style="border-bottom: 1px solid #E6E8EC"><div style="display: flex; flex-wrap: wrap; align-items: center; gap: 2px; min-height: 52px; max-width: 800px; box-sizing: border-box; padding: 4px 32px 4px 22px">
<a href="Compose.dc.html" style="display: inline-flex; align-items: center; gap: 6px; min-height: 44px; box-sizing: border-box; padding: 0 10px; border-radius: 8px; color: #344054; font-weight: 500; text-decoration: none">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 14 4 9 9 4"></polyline><path d="M20 20v-7a4 4 0 0 0-4-4H4"></path></svg>
<span>답장</span>
</a>
<a href="Compose.dc.html" style="display: inline-flex; align-items: center; gap: 6px; min-height: 44px; box-sizing: border-box; padding: 0 10px; border-radius: 8px; color: #344054; font-weight: 500; text-decoration: none">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="7 17 2 12 7 7"></polyline><polyline points="12 17 7 12 12 7"></polyline><path d="M22 18v-2a4 4 0 0 0-4-4H7"></path></svg>
<span>전체 답장</span>
</a>
<a href="Compose.dc.html" style="display: inline-flex; align-items: center; gap: 6px; min-height: 44px; box-sizing: border-box; padding: 0 10px; border-radius: 8px; color: #344054; font-weight: 500; text-decoration: none">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 14 20 9 15 4"></polyline><path d="M4 20v-7a4 4 0 0 1 4-4h12"></path></svg>
<span>전달</span>
</a>
</div></div>
<div style="display: flex; flex-direction: column; gap: 18px; max-width: 800px; box-sizing: border-box; padding: 20px 32px 36px">
<h1 id="mail-subject" style="margin: 4px 0 0; font-size: 20px; line-height: 1.4; font-weight: 650; letter-spacing: -0.015em"><sc-for list="{{ m.subParts }}" as="p" hint-placeholder-count="1"><sc-if value="{{ p.hit }}" hint-placeholder-val="{{ false }}"><mark>{{ p.t }}</mark></sc-if><sc-if value="{{ p.plain }}" hint-placeholder-val="{{ true }}"><span>{{ p.t }}</span></sc-if></sc-for></h1>
<div style="display: flex; align-items: flex-start; gap: 12px">
<span aria-hidden="true" style="flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: 50%; background: #E3F2F3; color: #0A5F67; font-size: 14px; font-weight: 600">{{ m.initial }}</span>
<div style="flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px">
<div style="display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px">
<span style="font-size: 13.5px; font-weight: 600">{{ m.fromName }}</span>
<span style="font-size: 12.5px; color: #667085">{{ m.fromEmail }}</span>
<span style="margin-left: auto; font-size: 12.5px; color: #667085; white-space: nowrap; font-variant-numeric: tabular-nums">{{ m.date }}</span>
</div>
<span style="font-size: 12.5px; color: #4A5363">받는 사람 {{ m.to }}</span>
</div>
</div>
<div style="white-space: pre-line; font-size: 14px; line-height: 1.75; color: #1F2733; max-width: 680px"><sc-for list="{{ m.bodyParts }}" as="p" hint-placeholder-count="1"><sc-if value="{{ p.hit }}" hint-placeholder-val="{{ false }}"><mark>{{ p.t }}</mark></sc-if><sc-if value="{{ p.plain }}" hint-placeholder-val="{{ true }}"><span>{{ p.t }}</span></sc-if></sc-for></div>
<sc-if value="{{ m.hasAtt }}" hint-placeholder-val="{{ true }}">
<section aria-labelledby="att-title" style="display: flex; flex-direction: column; gap: 8px; padding-top: 16px; border-top: 1px solid #EEF0F3">
<h2 id="att-title" style="margin: 0; font-size: 12.5px; font-weight: 600; color: #4A5363">첨부 파일 {{ m.attCount }}개</h2>
<ul style="list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(auto-fill, minmax(min(260px, 100%), 1fr)); gap: 8px">
<sc-for list="{{ m.atts }}" as="att" hint-placeholder-count="1">
<li style="display: flex; align-items: center; gap: 10px; padding: 4px 2px 4px 8px; border: 1px solid #E6E8EC; border-radius: 8px">
<span aria-hidden="true" style="flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 6px; background: #F2F4F7; color: #4A5363">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>
</span>
<div style="flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; line-height: 1.35">
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500"><sc-for list="{{ att.nameParts }}" as="p" hint-placeholder-count="1"><sc-if value="{{ p.hit }}" hint-placeholder-val="{{ false }}"><mark>{{ p.t }}</mark></sc-if><sc-if value="{{ p.plain }}" hint-placeholder-val="{{ true }}"><span>{{ p.t }}</span></sc-if></sc-for></span>
<span style="font-size: 12px; color: #667085">{{ att.size }}</span>
</div>
<button type="button" aria-label="{{ att.label }}" style="flex: 0 0 auto; width: 44px; height: 44px; display: inline-flex; align-items: center; justify-content: center; border: 0; border-radius: 8px; background: transparent; color: #344054; cursor: pointer">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
</button>
</li>
</sc-for>
</ul>
</section>
</sc-if>
</div>
</article>
</sc-if>
<sc-if value="{{ noSel }}" hint-placeholder-val="{{ false }}">
<div style="flex: 1 1 auto; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 6px; min-height: 520px; padding: 24px; text-align: center">
<svg width="36" height="36" viewBox="0 0 24 24" fill="none" stroke="#98A2B3" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
<p style="margin: 6px 0 0; font-size: 14px; font-weight: 600">메일을 골라 주세요</p>
<p style="margin: 0; font-size: 12.5px; color: #667085">검색 결과에서 메일을 고르면 여기에 내용이 보입니다.</p>
</div>
</sc-if>
</div>
</main>
</div>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props='{"variant":{"editor":"enum","options":["결과","조건 열림","조건으로 찾음","검색 준비 중","결과 없음"],"default":"결과"},"$preview":{"width":1440,"height":900}}'>
class Component extends DCLogic {
state = { scope: 'box', sel: 'r1', read: {}, condOpen: null, applied: null, period: 'month', attOnly: true };
results() {
return [
{
id: 'r1', box: 'inbox', who: '이도윤', fromName: '이도윤', fromEmail: 'doyun.lee@arasystem.it.kr', byDoyun: true,
to: '김아라 <ara.kim@arasystem.it.kr>', subject: 'Re: 유지보수 계약 갱신 건', time: '오전 10:42',
date: '2026. 11. 5. (목) 오전 10:42', unread: true,
preview: '지난번 말씀하신 계약서 초안을 첨부했습니다. 단가는 작년 계약과 같게 두었습니다.',
body: '김아라님, 안녕하세요.\n\n지난번 말씀하신 계약서 초안을 첨부했습니다.\n단가는 작년 계약과 같게 두었고, 기간만 1년 늘렸습니다.\n\n검토하시고 고칠 곳을 알려 주시면 바로 반영하겠습니다.\n\n이도윤 드림',
atts: [{ name: '유지보수_계약서_초안.docx', size: '48 KB' }],
},
{
id: 'r2', box: 'inbox', who: '인사팀', fromName: '인사팀', fromEmail: 'hr@arasystem.it.kr',
to: '전체 구성원 <all@arasystem.it.kr>', subject: '[공지] 계약직 채용 일정 안내', time: '오전 9:05',
date: '2026. 11. 5. (목) 오전 9:05', unread: true,
preview: '11월 계약직 채용은 아래 일정으로 진행합니다.',
body: '구성원 여러분, 인사팀입니다.\n\n11월 계약직 채용은 아래 일정으로 진행합니다.\n서류는 11월 12일(목)까지 받고, 면접은 그다음 주에 봅니다.',
atts: [],
},
{
id: 'r3', box: 'sent', who: '받는 사람: 정하늘', fromName: '김아라', fromEmail: 'ara.kim@arasystem.it.kr',
to: '정하늘 <haneul.jung@arasystem.it.kr>', subject: '회신: 공급 계약은 이번 주에 마무리합니다', time: '어제',
date: '2026. 11. 4. (수) 오후 5:48', unread: false,
preview: '말씀하신 공급 계약은 금요일 오전에 서명하기로 했습니다.',
body: '하늘님,\n\n말씀하신 공급 계약은 금요일 오전에 서명하기로 했습니다.\n계약을 마치면 사본을 공유 폴더에 올려 두겠습니다.\n\n김아라',
atts: [],
},
{
id: 'r4', box: 'inbox', who: '최서연', fromName: '최서연', fromEmail: 'seoyeon.choi@arasystem.it.kr',
to: '전체 구성원 <all@arasystem.it.kr>', subject: '11월 비용 정산 안내', time: '10월 30일',
date: '2026. 10. 30. (금) 오후 4:20', unread: false,
preview: '용역 계약 대금은 다음 달에 한꺼번에 정산합니다.',
body: '안녕하세요, 재무팀 최서연입니다.\n\n10월분 비용 마감은 11월 4일(수)입니다.\n용역 계약 대금은 다음 달에 한꺼번에 정산합니다.',
atts: [],
},
{
id: 'r5', box: 'inbox', who: '한유진', fromName: '한유진', fromEmail: 'yujin.han@partner.example.com',
to: '김아라 <ara.kim@arasystem.it.kr>', subject: '견적서와 계약 조건 정리', time: '10월 28일',
date: '2026. 10. 28. (수) 오전 11:15', unread: false,
preview: '요청하신 견적서와 계약 조건을 한 장으로 정리했습니다.',
body: '김아라 님께,\n\n요청하신 견적서와 계약 조건을 한 장으로 정리했습니다.\n계약 기간과 지급 조건은 2쪽에 있습니다.\n\n한유진 드림',
atts: [{ name: '견적서_계약조건.pdf', size: '310 KB' }],
},
];
}
parts(text, q) {
const out = [];
let i = 0;
while (i < text.length) {
const j = text.indexOf(q, i);
if (j < 0) { out.push({ t: text.slice(i), hit: false, plain: true }); break; }
if (j > i) out.push({ t: text.slice(i, j), hit: false, plain: true });
out.push({ t: q, hit: true, plain: false });
i = j + q.length;
}
return out.length ? out : [{ t: text, hit: false, plain: true }];
}
renderVals() {
const st = this.state;
const variant = this.props.variant ?? '결과';
const q = '계약';
const condOpen = st.condOpen ?? (variant === '조건 열림');
const applied = st.applied ?? (variant === '조건으로 찾음');
const isAll = st.scope === 'all';
const BOXNAME = { inbox: '받은편지함', sent: '보낸편지함' };
const PERIODS = [
{ v: 'all', t: '전체 기간' }, { v: 'week', t: '최근 1주' }, { v: 'month', t: '최근 1달' },
{ v: 'quarter', t: '최근 3달' }, { v: 'year', t: '최근 1년' }, { v: 'custom', t: '날짜 고르기' },
];
const periodLabel = st.period === 'custom' ? '2026. 10. 1.부터 11. 5.까지' : PERIODS.find((p) => p.v === st.period).t;
let list = this.results().filter((x) => isAll || x.box === 'inbox');
if (applied) list = list.filter((x) => x.byDoyun && (!st.attOnly || x.atts.length > 0));
if (variant === '결과 없음') list = [];
const isUnread = (x) => x.unread && !st.read[x.id];
const PANEL = [
{ id: 'inbox', name: '받은편지함', unread: 2 },
{ id: 'sent', name: '보낸편지함', unread: 0 },
{ id: 'drafts', name: '임시보관함', unread: 0 },
{ id: 'junk', name: '스팸메일함', unread: 0 },
{ id: 'trash', name: '휴지통', unread: 0 },
];
const boxes = PANEL.map((b) => {
const active = !isAll && b.id === 'inbox';
return {
name: b.name,
current: active ? 'page' : 'false',
bg: active ? '#E3F2F3' : 'transparent',
fg: active ? '#0A5F67' : '#344054',
fw: active ? '600' : '500',
countColor: active ? '#0A5F67' : '#171C26',
unread: b.unread,
hasUnread: b.unread > 0,
};
});
const items = list.map((x) => {
const selected = x.id === st.sel;
const unread = isUnread(x);
return {
who: x.who,
time: x.time,
hasAtt: x.atts.length > 0,
showBox: isAll,
boxName: BOXNAME[x.box],
subParts: this.parts(x.subject, q),
preParts: this.parts(x.preview, q),
bg: selected ? '#E3F2F3' : '#FFFFFF',
fw: unread ? '650' : '450',
whoColor: unread ? '#171C26' : '#344054',
subFw: unread ? '600' : '400',
timeFw: unread ? '600' : '400',
timeColor: unread ? '#171C26' : '#667085',
pick: () => this.setState({ sel: x.id, read: { ...st.read, [x.id]: true } }),
};
});
const cur = list.find((x) => x.id === st.sel) || list[0] || null;
const base = cur || { subject: '', fromName: '', fromEmail: '', to: '', date: '', body: '', atts: [] };
const m = {
subParts: this.parts(base.subject, q),
bodyParts: this.parts(base.body, q),
fromName: base.fromName,
fromEmail: base.fromEmail,
to: base.to,
date: base.date,
initial: base.fromName ? base.fromName.slice(0, 1) : '',
atts: base.atts.map((a) => ({ nameParts: this.parts(a.name, q), size: a.size, label: a.name + ' 내려받기' })),
attCount: base.atts.length,
hasAtt: base.atts.length > 0,
};
return {
boxes: boxes,
items: items,
countText: `‘${q}’ · ${list.length}통`,
condOpen: condOpen,
condOpenStr: condOpen ? 'true' : 'false',
condBtnBg: condOpen || applied ? '#E3F2F3' : 'transparent',
condBtnFg: condOpen || applied ? '#0A5F67' : '#4A5363',
toggleCond: () => this.setState({ condOpen: !condOpen }),
applyCond: () => this.setState({ condOpen: false, applied: true }),
clearCond: () => this.setState({ condOpen: false, applied: false, period: 'month', attOnly: true }),
period: st.period,
periods: PERIODS,
setPeriod: (e) => this.setState({ period: e.target.value }),
isCustom: st.period === 'custom',
attOnly: st.attOnly,
toggleAtt: () => this.setState({ attOnly: !st.attOnly }),
applied: applied,
condSummary: `조건: 이도윤 님이 보낸 메일, ${periodLabel}${st.attOnly ? ', 첨부 있음' : ''}`,
showIndexing: variant === '검색 준비 중',
isAll: isAll,
boxPressed: isAll ? 'false' : 'true',
allPressed: isAll ? 'true' : 'false',
scopeBox: () => this.setState({ scope: 'box', sel: 'r1' }),
scopeAll: () => this.setState({ scope: 'all', sel: 'r1' }),
showList: list.length > 0,
showEmpty: list.length === 0,
offerAll: !isAll,
hasSel: !!cur,
noSel: !cur,
m: m,
};
}
}
</script>
</body>
</html>
+304
View File
@@ -0,0 +1,304 @@
<!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<title>읽기 화면 · 같은 대화</title>
<script src="./support.js"></script>
</head>
<body>
<x-dc>
<helmet>
<style>
@font-face{font-family:'Pretendard';src:url('/_blob/16067b9aa5137abf808e3dbef30c42e0') format('woff2-variations'),url('/_blob/16067b9aa5137abf808e3dbef30c42e0') format('woff2');font-weight:45 920;font-style:normal;font-display:swap}
body{margin:0}
a{color:#0E7C86}a:hover{color:#0A5F67}
:focus-visible{outline:2px solid #0E7C86;outline-offset:2px}
.row:hover{background:#F8F9FB}
</style>
</helmet>
<div style="min-height: 100vh; display: flex; background: #FFFFFF; color: #171C26; font-family: Pretendard, -apple-system, 'Apple SD Gothic Neo', 'Malgun Gothic', system-ui, sans-serif; font-size: 13px; line-height: 1.5">
<nav aria-label="사내 시스템" style="flex: 0 0 72px; box-sizing: border-box; display: flex; flex-direction: column; align-items: center; gap: 14px; padding: 10px 0 12px; background: #0B3640">
<a href="Main.dc.html" style="display: inline-flex; align-items: center; justify-content: center; width: 60px; min-height: 44px; border-radius: 10px; text-decoration: none">
<img src="/_blob/2b9ef4349333debc969432c859eaf1bf" alt="ARA SYSTEM" width="48" height="17" style="display: block; width: 48px; height: auto">
</a>
<ul style="list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px">
<li>
<a href="Main.dc.html" aria-current="page" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; width: 60px; min-height: 56px; border-radius: 10px; background: #155A64; color: #FFFFFF; font-size: 11px; font-weight: 600; text-decoration: none">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 4h16c1.1 0 2 .9 2 2v12c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2z"></path><polyline points="22,6 12,13 2,6"></polyline></svg>
<span>메일</span>
</a>
</li>
<li>
<a href="#" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; width: 60px; min-height: 56px; border-radius: 10px; color: #A3C2C6; font-size: 11px; font-weight: 500; text-decoration: none">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"></rect><line x1="16" y1="2" x2="16" y2="6"></line><line x1="8" y1="2" x2="8" y2="6"></line><line x1="3" y1="10" x2="21" y2="10"></line></svg>
<span>캘린더</span>
</a>
</li>
<li>
<a href="#" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; width: 60px; min-height: 56px; border-radius: 10px; color: #A3C2C6; font-size: 11px; font-weight: 500; text-decoration: none">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z"></path></svg>
<span>파일</span>
</a>
</li>
<li>
<a href="#" style="display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; width: 60px; min-height: 56px; border-radius: 10px; color: #A3C2C6; font-size: 11px; font-weight: 500; text-decoration: none">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"></path><circle cx="9" cy="7" r="4"></circle><path d="M23 21v-2a4 4 0 0 0-3-3.87"></path><path d="M16 3.13a4 4 0 0 1 0 7.75"></path></svg>
<span>주소록</span>
</a>
</li>
</ul>
<a href="Settings.dc.html" style="margin-top: auto; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 3px; width: 60px; min-height: 56px; border-radius: 10px; color: #A3C2C6; font-size: 11px; font-weight: 500; text-decoration: none">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="3"></circle><path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z"></path></svg>
<span>설정</span>
</a>
</nav>
<div style="flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column">
<header style="display: flex; align-items: center; flex-wrap: wrap; gap: 8px 16px; box-sizing: border-box; min-height: 56px; padding: 6px 12px 6px 20px; border-bottom: 1px solid #E6E8EC">
<h1 style="margin: 0; font-size: 16px; font-weight: 700; letter-spacing: -0.01em">메일</h1>
<form role="search" aria-label="메일 검색" style="flex: 1 1 280px; max-width: 520px; display: flex; align-items: center; gap: 8px; box-sizing: border-box; height: 40px; margin: 0; padding: 0 12px; border: 1px solid #D0D5DD; border-radius: 8px; background: #FFFFFF">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#667085" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg>
<input type="search" aria-label="메일 검색" placeholder="메일 검색" style="flex: 1 1 auto; min-width: 0; border: 0; outline: none; background: transparent; font: inherit; color: #171C26">
</form>
<div style="margin-left: auto; display: flex; align-items: center; gap: 10px">
<span aria-hidden="true" style="display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 50%; background: #E3F2F3; color: #0A5F67; font-size: 13px; font-weight: 600">김</span>
<div style="display: flex; flex-direction: column; line-height: 1.3">
<span style="font-weight: 600">김아라</span>
<span style="font-size: 12px; color: #667085">ara.kim@arasystem.it.kr</span>
</div>
<a href="Login.dc.html" style="display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 10px; border-radius: 8px; color: #4A5363; font-weight: 500; text-decoration: none">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"></path><polyline points="16 17 21 12 16 7"></polyline><line x1="21" y1="12" x2="9" y2="12"></line></svg>
<span>로그아웃</span>
</a>
</div>
</header>
<div style="flex: 1 1 auto; display: flex; flex-wrap: wrap; align-items: stretch">
<nav aria-label="메일함" style="flex: 1 1 208px; box-sizing: border-box; display: flex; flex-direction: column; gap: 14px; padding: 14px 10px; background: #F8F9FB; border-right: 1px solid #E6E8EC">
<a href="Compose.dc.html" style="display: flex; align-items: center; justify-content: center; gap: 8px; min-height: 44px; border-radius: 8px; background: #0E7C86; color: #FFFFFF; font-size: 13.5px; font-weight: 600; text-decoration: none">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 20h9"></path><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"></path></svg>
<span>메일 쓰기</span>
</a>
<ul style="list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 1px">
<li><a href="Main.dc.html" aria-current="page" style="display: flex; align-items: center; gap: 10px; min-height: 40px; box-sizing: border-box; padding: 0 10px; border-radius: 7px; background: #E3F2F3; color: #0A5F67; font-weight: 600; text-decoration: none"><span style="flex: 1 1 auto">받은편지함</span><span style="font-size: 12px; font-weight: 600; font-variant-numeric: tabular-nums">2</span></a></li>
<li><a href="#" style="display: flex; align-items: center; min-height: 40px; box-sizing: border-box; padding: 0 10px; border-radius: 7px; color: #344054; font-weight: 500; text-decoration: none">보낸편지함</a></li>
<li><a href="#" style="display: flex; align-items: center; min-height: 40px; box-sizing: border-box; padding: 0 10px; border-radius: 7px; color: #344054; font-weight: 500; text-decoration: none">임시보관함</a></li>
<li><a href="#" style="display: flex; align-items: center; min-height: 40px; box-sizing: border-box; padding: 0 10px; border-radius: 7px; color: #344054; font-weight: 500; text-decoration: none">스팸메일함</a></li>
<li><a href="#" style="display: flex; align-items: center; min-height: 40px; box-sizing: border-box; padding: 0 10px; border-radius: 7px; color: #344054; font-weight: 500; text-decoration: none">휴지통</a></li>
</ul>
</nav>
<main style="flex: 999 1 560px; min-width: 0; display: flex; flex-wrap: wrap; align-items: stretch">
<section aria-labelledby="list-title" style="flex: 1 1 360px; min-width: 0; box-sizing: border-box; display: flex; flex-direction: column; border-right: 1px solid #E6E8EC">
<div style="display: flex; align-items: center; flex-wrap: wrap; gap: 6px 8px; min-height: 52px; box-sizing: border-box; padding: 4px 8px 4px 16px; border-bottom: 1px solid #E6E8EC">
<h2 id="list-title" style="margin: 0; font-size: 15px; font-weight: 700; letter-spacing: -0.01em">받은편지함</h2>
<span style="font-size: 12px; color: #667085">안 읽은 메일 2통</span>
<a href="ListThreads14.dc.html" style="margin-left: auto; display: inline-flex; align-items: center; gap: 6px; min-height: 36px; box-sizing: border-box; padding: 0 10px; border: 1px solid #D0D5DD; border-radius: 8px; background: #FFFFFF; color: #344054; font-size: 12.5px; font-weight: 600; text-decoration: none">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"></path></svg>
<span>대화로 묶기</span>
</a>
</div>
<ul aria-label="받은편지함 메일" style="list-style: none; margin: 0; padding: 0">
<sc-for list="{{ items }}" as="item" hint-placeholder-count="6">
<li style="border-bottom: 1px solid #EEF0F3; background: {{ item.bg }}">
<a href="#" style="display: flex; flex-direction: column; justify-content: center; gap: 1px; min-height: 52px; box-sizing: border-box; padding: 6px 16px; color: inherit; text-decoration: none">
<span style="display: flex; align-items: center; gap: 6px">
<span style="flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: {{ item.fw }}">{{ item.who }}</span>
<span style="flex: 0 0 auto; font-size: 12px; color: {{ item.timeColor }}; font-weight: {{ item.timeFw }}; font-variant-numeric: tabular-nums">{{ item.time }}</span>
</span>
<span style="display: flex; align-items: center; gap: 6px; min-width: 0">
<span style="min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: {{ item.subFw }}">{{ item.subject }}</span>
<sc-if value="{{ item.hasAtt }}" hint-placeholder-val="{{ false }}"><svg role="img" aria-label="첨부 있음" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="#667085" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="flex: 0 0 auto"><path d="M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48"></path></svg></sc-if>
</span>
</a>
</li>
</sc-for>
</ul>
</section>
<div style="flex: 999 1 480px; min-width: 0; box-sizing: border-box; display: flex; flex-direction: column">
<article aria-labelledby="mail-subject" style="display: flex; flex-direction: column">
<div style="border-bottom: 1px solid #E6E8EC"><div style="display: flex; flex-wrap: wrap; align-items: center; gap: 2px; min-height: 52px; max-width: 800px; box-sizing: border-box; padding: 4px 32px 4px 22px">
<a href="Compose.dc.html" style="display: inline-flex; align-items: center; gap: 6px; min-height: 44px; box-sizing: border-box; padding: 0 10px; border-radius: 8px; color: #344054; font-weight: 500; text-decoration: none">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="9 14 4 9 9 4"></polyline><path d="M20 20v-7a4 4 0 0 0-4-4H4"></path></svg>
<span>답장</span>
</a>
<a href="Compose.dc.html" style="display: inline-flex; align-items: center; gap: 6px; min-height: 44px; box-sizing: border-box; padding: 0 10px; border-radius: 8px; color: #344054; font-weight: 500; text-decoration: none">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="7 17 2 12 7 7"></polyline><polyline points="12 17 7 12 12 7"></polyline><path d="M22 18v-2a4 4 0 0 0-4-4H7"></path></svg>
<span>전체 답장</span>
</a>
<a href="Compose.dc.html" style="display: inline-flex; align-items: center; gap: 6px; min-height: 44px; box-sizing: border-box; padding: 0 10px; border-radius: 8px; color: #344054; font-weight: 500; text-decoration: none">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="15 14 20 9 15 4"></polyline><path d="M4 20v-7a4 4 0 0 1 4-4h12"></path></svg>
<span>전달</span>
</a>
</div></div>
<div style="display: flex; flex-direction: column; gap: 18px; max-width: 800px; box-sizing: border-box; padding: 20px 32px 36px">
<h1 id="mail-subject" style="margin: 4px 0 0; font-size: 20px; line-height: 1.4; font-weight: 650; letter-spacing: -0.015em">Re: 유지보수 계약 갱신 건</h1>
<div style="display: flex; align-items: flex-start; gap: 12px">
<span aria-hidden="true" style="flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 36px; height: 36px; border-radius: 50%; background: #E3F2F3; color: #0A5F67; font-size: 14px; font-weight: 600">이</span>
<div style="flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; gap: 1px">
<div style="display: flex; flex-wrap: wrap; align-items: baseline; gap: 2px 8px">
<span style="font-size: 13.5px; font-weight: 600">이도윤</span>
<span style="font-size: 12.5px; color: #667085">doyun.lee@arasystem.it.kr</span>
<span style="margin-left: auto; font-size: 12.5px; color: #667085; white-space: nowrap; font-variant-numeric: tabular-nums">2026. 11. 5. (목) 오전 10:42</span>
</div>
<span style="font-size: 12.5px; color: #4A5363">받는 사람 김아라 &lt;ara.kim@arasystem.it.kr&gt;</span>
</div>
</div>
<div style="white-space: pre-line; font-size: 14px; line-height: 1.75; color: #1F2733; max-width: 680px">김아라님, 안녕하세요.
말씀하신 조건으로 계약서 초안을 고쳐 첨부했습니다.
기간은 1년이고, 단가는 작년과 같습니다.
이번 주 안에 회신 주시면 다음 주 월요일에 서명하겠습니다.
이도윤 드림</div>
<section aria-labelledby="att-title" style="display: flex; flex-direction: column; gap: 8px; padding-top: 16px; border-top: 1px solid #EEF0F3">
<h2 id="att-title" style="margin: 0; font-size: 12.5px; font-weight: 600; color: #4A5363">첨부 파일 1개</h2>
<div style="display: flex; align-items: center; gap: 10px; max-width: 320px; padding: 4px 2px 4px 8px; border: 1px solid #E6E8EC; border-radius: 8px">
<span aria-hidden="true" style="flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 32px; height: 32px; border-radius: 6px; background: #F2F4F7; color: #4A5363">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V9z"></path><polyline points="13 2 13 9 20 9"></polyline></svg>
</span>
<div style="flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; line-height: 1.35">
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 500">유지보수_계약서_수정본.docx</span>
<span style="font-size: 12px; color: #667085">52 KB</span>
</div>
<button type="button" aria-label="유지보수_계약서_수정본.docx 내려받기" style="flex: 0 0 auto; width: 44px; height: 44px; display: inline-flex; align-items: center; justify-content: center; border: 0; border-radius: 8px; background: transparent; color: #344054; cursor: pointer">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.75" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"></path><polyline points="7 10 12 15 17 10"></polyline><line x1="12" y1="15" x2="12" y2="3"></line></svg>
</button>
</div>
</section>
<sc-if value="{{ hasConv }}" hint-placeholder-val="{{ true }}">
<section aria-labelledby="conv-title" style="display: flex; flex-direction: column; gap: 8px; padding-top: 16px; border-top: 1px solid #EEF0F3">
<h2 id="conv-title" style="margin: 0; font-size: 12.5px; font-weight: 600; color: #4A5363">같은 대화 {{ convCount }}통</h2>
<ol style="list-style: none; margin: 0; padding: 0; border: 1px solid #E6E8EC; border-radius: 8px; overflow: hidden">
<sc-for list="{{ conv }}" as="c" hint-placeholder-count="4">
<li style="border-top: {{ c.borderTop }}">
<sc-if value="{{ c.isCurrent }}" hint-placeholder-val="{{ false }}">
<div aria-current="true" style="display: flex; align-items: center; gap: 10px; min-height: 48px; box-sizing: border-box; padding: 6px 14px 6px 40px; background: #F2F8F8">
<span aria-hidden="true" style="flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 50%; background: #E3F2F3; color: #0A5F67; font-size: 12px; font-weight: 600">{{ c.initial }}</span>
<span style="flex: 0 0 auto; font-weight: 600">{{ c.who }}</span>
<span style="flex: 1 1 auto; min-width: 0; font-size: 12.5px; color: #0A5F67; font-weight: 600">지금 보고 있는 메일</span>
<span style="flex: 0 0 auto; font-size: 12px; color: #667085; font-variant-numeric: tabular-nums">{{ c.date }}</span>
</div>
</sc-if>
<sc-if value="{{ c.isOther }}" hint-placeholder-val="{{ true }}">
<button type="button" class="row" aria-expanded="{{ c.expandedStr }}" onClick="{{ c.toggle }}" style="width: 100%; display: flex; align-items: center; gap: 10px; min-height: 48px; box-sizing: border-box; padding: 6px 14px; border: 0; background: #FFFFFF; font: inherit; color: inherit; text-align: left; cursor: pointer">
<sc-if value="{{ c.expanded }}" hint-placeholder-val="{{ false }}"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#667085" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="flex: 0 0 auto"><polyline points="6 9 12 15 18 9"></polyline></svg></sc-if>
<sc-if value="{{ c.collapsed }}" hint-placeholder-val="{{ true }}"><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#667085" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" style="flex: 0 0 auto"><polyline points="9 18 15 12 9 6"></polyline></svg></sc-if>
<span aria-hidden="true" style="flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; border-radius: 50%; background: {{ c.avatarBg }}; color: {{ c.avatarFg }}; font-size: 12px; font-weight: 600">{{ c.initial }}</span>
<span style="flex: 0 0 auto; font-weight: 600">{{ c.who }}</span>
<sc-if value="{{ c.isSent }}" hint-placeholder-val="{{ false }}"><span style="flex: 0 0 auto; font-size: 12px; color: #667085">보낸편지함</span></sc-if>
<span style="flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12.5px; color: #667085">{{ c.preview }}</span>
<span style="flex: 0 0 auto; font-size: 12px; color: #667085; font-variant-numeric: tabular-nums">{{ c.date }}</span>
</button>
<sc-if value="{{ c.expanded }}" hint-placeholder-val="{{ false }}">
<div style="display: flex; flex-direction: column; gap: 10px; padding: 2px 16px 14px 78px">
<span style="font-size: 12.5px; color: #4A5363">받는 사람 {{ c.to }}</span>
<span style="font-size: 12.5px; color: #667085">제목 {{ c.subject }}</span>
<div style="white-space: pre-line; font-size: 13.5px; line-height: 1.7; color: #1F2733">{{ c.body }}</div>
<div style="display: flex; flex-wrap: wrap; gap: 4px; margin-left: -10px">
<a href="Compose.dc.html" style="display: inline-flex; align-items: center; min-height: 36px; padding: 0 10px; border-radius: 8px; color: #0E7C86; font-weight: 600; text-decoration: none">답장</a>
<a href="#" style="display: inline-flex; align-items: center; min-height: 36px; padding: 0 10px; border-radius: 8px; color: #0E7C86; font-weight: 600; text-decoration: none">이 메일만 열기</a>
</div>
</div>
</sc-if>
</sc-if>
</li>
</sc-for>
</ol>
</section>
</sc-if>
</div>
</article>
</div>
</main>
</div>
</div>
</div>
</x-dc>
<script type="text/x-dc" data-dc-script data-props='{"variant":{"editor":"enum","options":["접힘","하나 펼침","한 통뿐"],"default":"접힘"},"$preview":{"width":1440,"height":900}}'>
class Component extends DCLogic {
state = { open: null };
convItems() {
return [
{
id: 'c1', who: '나', initial: '김', sent: true, date: '10월 27일 (화) 오후 2:10',
subject: '유지보수 계약 갱신 건', to: '이도윤 <doyun.lee@arasystem.it.kr>',
preview: '다음 달에 끝나는 유지보수 계약을 1년 더 이어 가려고 합니다.',
body: '이도윤님,\n\n다음 달에 끝나는 유지보수 계약을 1년 더 이어 가려고 합니다.\n조건을 정리해서 보내 주시면 검토하겠습니다.\n\n김아라',
},
{
id: 'c2', who: '이도윤', initial: '이', sent: false, date: '10월 29일 (목) 오전 9:30',
subject: '회신: 유지보수 계약 갱신 건', to: '김아라 <ara.kim@arasystem.it.kr>',
preview: '네, 확인했습니다. 단가와 기간을 정리해서 다음 주 초에 보내 드리겠습니다.',
body: '네, 확인했습니다.\n단가와 기간을 정리해서 다음 주 초에 보내 드리겠습니다.\n\n이도윤',
},
{
id: 'c3', who: '나', initial: '김', sent: true, date: '10월 29일 (목) 오후 1:05',
subject: '회신: 유지보수 계약 갱신 건', to: '이도윤 <doyun.lee@arasystem.it.kr>',
preview: '감사합니다. 초안은 다음 주 초까지 받으면 됩니다.',
body: '감사합니다.\n초안은 다음 주 초까지 받으면 됩니다.\n\n김아라',
},
{ id: 'c4', who: '이도윤', initial: '이', current: true, date: '오늘 오전 10:42' },
];
}
renderVals() {
const st = this.state;
const variant = this.props.variant ?? '접힘';
const open = st.open ?? (variant === '하나 펼침' ? { c2: true } : {});
const conv = this.convItems().map((c, i) => {
const expanded = !!open[c.id];
return {
...c,
isCurrent: !!c.current,
isOther: !c.current,
isSent: !!c.sent,
expanded: expanded,
collapsed: !expanded,
expandedStr: expanded ? 'true' : 'false',
borderTop: i === 0 ? '0' : '1px solid #EEF0F3',
avatarBg: c.sent ? '#F2F4F7' : '#E3F2F3',
avatarFg: c.sent ? '#4A5363' : '#0A5F67',
toggle: () => this.setState({ open: { ...open, [c.id]: !expanded } }),
};
});
const LIST = [
{ who: '이도윤', time: '오전 10:42', subject: 'Re: 유지보수 계약 갱신 건', unread: true, att: true, selected: true },
{ who: '인사팀', time: '오전 9:05', subject: '[공지] 계약직 채용 일정 안내', unread: true },
{ who: '박준호', time: '어제', subject: '11월 인프라 점검 일정 조율' },
{ who: '최서연', time: '10월 30일', subject: '11월 비용 정산 안내' },
{ who: '이도윤', time: '10월 29일', subject: '회신: 유지보수 계약 갱신 건' },
{ who: '정하늘', time: '10월 28일', subject: '신규 입사자 교육 회의록' },
];
const items = LIST.map((x) => ({
who: x.who,
time: x.time,
subject: x.subject,
hasAtt: !!x.att,
bg: x.selected ? '#E3F2F3' : '#FFFFFF',
fw: x.unread ? '650' : '450',
subFw: x.unread ? '600' : '400',
timeFw: x.unread ? '600' : '400',
timeColor: x.unread ? '#171C26' : '#667085',
}));
return {
items: items,
conv: conv,
convCount: conv.length,
hasConv: variant !== '한 통뿐',
};
}
}
</script>
</body>
</html>
+7 -3
View File
@@ -46,9 +46,12 @@
"LoginPasswordChange.dc.html": { "x": 0, "y": 25610, "w": 1440, "h": 2900, "title": "로그인 · 새 비밀번호 정하기" },
"LoginTwoFactorSetup.dc.html": { "x": 1520, "y": 25610, "w": 1440, "h": 4290, "title": "로그인 · 2단계 인증 켜기(필수)" },
"SettingsMailClients.dc.html": { "x": 3040, "y": 25610, "w": 1440, "h": 3600, "title": "설정 · 메일 프로그램 연결" },
"MailNoticesCutover.dc.html": { "x": 4560, "y": 25610, "w": 1440, "h": 3780, "title": "알림 · 안내 모음(13단계)" }
"MailNoticesCutover.dc.html": { "x": 4560, "y": 25610, "w": 1440, "h": 3780, "title": "알림 · 안내 모음(13단계)" },
"Search14.dc.html": { "x": 0, "y": 30500, "w": 1440, "h": 900, "title": "메일 검색 · 조건 검색 · 강조", "expand": "fill", "is_interactive": true },
"Thread14.dc.html": { "x": 1520, "y": 30500, "w": 1440, "h": 900, "title": "읽기 화면 · 같은 대화", "expand": "fill", "is_interactive": true },
"ListThreads14.dc.html": { "x": 3040, "y": 30500, "w": 1440, "h": 900, "title": "목록 · 대화로 묶어 보기", "expand": "fill", "is_interactive": true }
},
"order": ["Main.dc.html", "Login.dc.html", "Compose.dc.html", "States.dc.html", "MainList.dc.html", "MainListRead.dc.html", "ComposeEditor.dc.html", "Settings.dc.html", "ComposeAssist.dc.html", "Search.dc.html", "BulkSelect.dc.html", "ListStates.dc.html", "SettingsAccount.dc.html", "SettingsVacation.dc.html", "MailboxManage.dc.html", "MailActions4.dc.html", "SettingsNotify.dc.html", "MailRealtime.dc.html", "SettingsFilter.dc.html", "MailFilterStates.dc.html", "MailFilterApply.dc.html", "MailFilterApplyStates.dc.html", "Contacts.dc.html", "ContactsEdit.dc.html", "ContactsMail.dc.html", "MailReadTools.dc.html", "MailPrint.dc.html", "MailSendLater.dc.html", "SettingsSendRead.dc.html", "ComposeLargeFiles.dc.html", "LargeFileBlock.dc.html", "MailReadLargeFiles.dc.html", "SharePage.dc.html", "SettingsLargeFiles.dc.html", "LoginTwoFactor.dc.html", "SettingsTwoFactor.dc.html", "SettingsAppPasswords.dc.html", "SettingsLoginHistory.dc.html", "LoginPasswordChange.dc.html", "LoginTwoFactorSetup.dc.html", "SettingsMailClients.dc.html", "MailNoticesCutover.dc.html"],
"order": ["Main.dc.html", "Login.dc.html", "Compose.dc.html", "States.dc.html", "MainList.dc.html", "MainListRead.dc.html", "ComposeEditor.dc.html", "Settings.dc.html", "ComposeAssist.dc.html", "Search.dc.html", "BulkSelect.dc.html", "ListStates.dc.html", "SettingsAccount.dc.html", "SettingsVacation.dc.html", "MailboxManage.dc.html", "MailActions4.dc.html", "SettingsNotify.dc.html", "MailRealtime.dc.html", "SettingsFilter.dc.html", "MailFilterStates.dc.html", "MailFilterApply.dc.html", "MailFilterApplyStates.dc.html", "Contacts.dc.html", "ContactsEdit.dc.html", "ContactsMail.dc.html", "MailReadTools.dc.html", "MailPrint.dc.html", "MailSendLater.dc.html", "SettingsSendRead.dc.html", "ComposeLargeFiles.dc.html", "LargeFileBlock.dc.html", "MailReadLargeFiles.dc.html", "SharePage.dc.html", "SettingsLargeFiles.dc.html", "LoginTwoFactor.dc.html", "SettingsTwoFactor.dc.html", "SettingsAppPasswords.dc.html", "SettingsLoginHistory.dc.html", "LoginPasswordChange.dc.html", "LoginTwoFactorSetup.dc.html", "SettingsMailClients.dc.html", "MailNoticesCutover.dc.html", "Search14.dc.html", "Thread14.dc.html", "ListThreads14.dc.html"],
"notes": {
"flow": { "x": 0, "y": -300, "text": "arasystem 웹메일 — 1단계 화면 (로그인 → 메일함 → 쓰기)", "kind": "title1", "maxW": 4480 },
"phase2": { "x": 0, "y": 2180, "text": "2단계 화면 — 임시 저장 · 받는 사람 자동 완성 · 서명 (2026-10-27 확정)", "kind": "title1", "maxW": 2960 },
@@ -61,7 +64,8 @@
"phase9": { "x": 0, "y": 16520, "text": "9단계 화면 — 보내기 취소 · 예약 발송 · 외부 이미지 보기 · 인쇄 · 보낸 사람 차단 — 2026-10-29 확정", "kind": "title1", "maxW": 6000 },
"phase11": { "x": 0, "y": 18620, "text": "11단계 화면 — 대용량 첨부 (쓰기 · 본문 덩이 · 읽기 · 내려받기 화면 · 설정) — 2026-10-30 확정", "kind": "title1", "maxW": 7520 },
"phase12": { "x": 0, "y": 21580, "text": "12단계 화면 — 계정 보안(2단계 인증 · 앱 비밀번호 · 로그인 기록) — 2026-10-31 확정", "kind": "title1", "maxW": 6000 },
"phase13": { "x": 0, "y": 25310, "text": "13단계 화면 — 전환 준비(새 비밀번호 · 2단계 필수 · 메일 프로그램 연결 · 알림) — 2026-10-31 확정", "kind": "title1", "maxW": 6000 }
"phase13": { "x": 0, "y": 25310, "text": "13단계 화면 — 전환 준비(새 비밀번호 · 2단계 필수 · 메일 프로그램 연결 · 알림) — 2026-10-31 확정", "kind": "title1", "maxW": 6000 },
"phase14": { "x": 0, "y": 30200, "text": "14단계 화면 — 검색 · 조건 검색 · 강조 · 같은 대화 · 대화로 묶어 보기 — 2026-11-05 확정", "kind": "title1", "maxW": 4480 }
},
"designSystems": []
}