이메일 내용 iframe으로 변경
This commit is contained in:
parent
730ea717bf
commit
54f6dce97d
1 changed files with 115 additions and 30 deletions
|
|
@ -2,7 +2,7 @@
|
||||||
* 메일 상세 보기 컴포넌트
|
* 메일 상세 보기 컴포넌트
|
||||||
* 선택된 메일의 내용, 첨부파일, 액션 버튼 표시
|
* 선택된 메일의 내용, 첨부파일, 액션 버튼 표시
|
||||||
*/
|
*/
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useRef } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { useMail } from '../context/MailContext';
|
import { useMail } from '../context/MailContext';
|
||||||
import { Trash2, Printer, Star, Reply, Forward, Mail as MailIcon, MailOpen, Archive, Paperclip, Download, FileText, Image, File, Edit, AlertOctagon, Languages, Sparkles } from 'lucide-react';
|
import { Trash2, Printer, Star, Reply, Forward, Mail as MailIcon, MailOpen, Archive, Paperclip, Download, FileText, Image, File, Edit, AlertOctagon, Languages, Sparkles } from 'lucide-react';
|
||||||
|
|
@ -15,6 +15,103 @@ import { encodeEmailId } from '../utils/emailIdEncoder';
|
||||||
import { HighlightText } from '../utils/highlightText';
|
import { HighlightText } from '../utils/highlightText';
|
||||||
import ConfirmDialog from './ConfirmDialog';
|
import ConfirmDialog from './ConfirmDialog';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 이메일 HTML을 iframe 안에서 렌더링하는 컴포넌트 (CSS 격리)
|
||||||
|
*/
|
||||||
|
const EmailIframe = ({ htmlContent }) => {
|
||||||
|
const iframeRef = useRef(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (iframeRef.current && htmlContent) {
|
||||||
|
const doc = iframeRef.current.contentDocument;
|
||||||
|
if (doc) {
|
||||||
|
doc.open();
|
||||||
|
doc.write(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<base target="_blank">
|
||||||
|
<style>
|
||||||
|
html { height: auto !important; overflow: visible !important; }
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: #333;
|
||||||
|
height: auto !important;
|
||||||
|
overflow: visible !important;
|
||||||
|
}
|
||||||
|
img { max-width: 100%; height: auto; }
|
||||||
|
a { color: #2563eb; }
|
||||||
|
* { max-width: 100%; box-sizing: border-box; }
|
||||||
|
table { height: auto !important; }
|
||||||
|
div, td, tr { height: auto !important; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>${htmlContent}</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
doc.close();
|
||||||
|
|
||||||
|
// iframe 높이 자동 조절 - 실제 콘텐츠 높이 측정
|
||||||
|
const resizeIframe = () => {
|
||||||
|
if (iframeRef.current && doc.body) {
|
||||||
|
// 모든 자식 요소의 실제 위치를 기반으로 높이 계산
|
||||||
|
const children = doc.body.children;
|
||||||
|
let maxBottom = 0;
|
||||||
|
|
||||||
|
for (let child of children) {
|
||||||
|
const rect = child.getBoundingClientRect();
|
||||||
|
const bottom = rect.top + rect.height;
|
||||||
|
if (bottom > maxBottom) {
|
||||||
|
maxBottom = bottom;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 최소 100px, 최대 계산된 높이 + 여유 20px
|
||||||
|
const height = Math.max(maxBottom + 20, 100);
|
||||||
|
iframeRef.current.style.height = height + 'px';
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 이미지 로드 후 재조정
|
||||||
|
const images = doc.images;
|
||||||
|
let loadedCount = 0;
|
||||||
|
const totalImages = images.length;
|
||||||
|
|
||||||
|
if (totalImages === 0) {
|
||||||
|
resizeIframe();
|
||||||
|
} else {
|
||||||
|
for (let img of images) {
|
||||||
|
img.onload = img.onerror = () => {
|
||||||
|
loadedCount++;
|
||||||
|
resizeIframe();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 초기 크기 설정 + 지연 재조정
|
||||||
|
setTimeout(resizeIframe, 50);
|
||||||
|
setTimeout(resizeIframe, 200);
|
||||||
|
setTimeout(resizeIframe, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [htmlContent]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<iframe
|
||||||
|
ref={iframeRef}
|
||||||
|
title="email-content"
|
||||||
|
className="w-full border-0 mb-8"
|
||||||
|
style={{ minHeight: '100px', overflow: 'hidden' }}
|
||||||
|
sandbox="allow-same-origin allow-popups allow-popups-to-escape-sandbox"
|
||||||
|
scrolling="no"
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const MailDetail = ({ onContinueDraft, onReply, onForward }) => {
|
const MailDetail = ({ onContinueDraft, onReply, onForward }) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { selectedEmail, setSelectedEmail, toggleStar, moveToTrash, markAsUnread, markAsRead, restoreEmail, deleteEmail, selectedBox, moveToSpam, moveEmail, userNames, isSearchMode, searchQuery } = useMail();
|
const { selectedEmail, setSelectedEmail, toggleStar, moveToTrash, markAsUnread, markAsRead, restoreEmail, deleteEmail, selectedBox, moveToSpam, moveEmail, userNames, isSearchMode, searchQuery } = useMail();
|
||||||
|
|
@ -590,19 +687,9 @@ const MailDetail = ({ onContinueDraft, onReply, onForward }) => {
|
||||||
const textHasHtml = textContent && /<[a-z][\s\S]*>/i.test(textContent);
|
const textHasHtml = textContent && /<[a-z][\s\S]*>/i.test(textContent);
|
||||||
|
|
||||||
if (htmlContent) {
|
if (htmlContent) {
|
||||||
return (
|
return <EmailIframe htmlContent={htmlContent} />;
|
||||||
<div
|
|
||||||
className="email-html-content mb-8 overflow-x-auto"
|
|
||||||
dangerouslySetInnerHTML={{ __html: htmlContent }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else if (textHasHtml) {
|
} else if (textHasHtml) {
|
||||||
return (
|
return <EmailIframe htmlContent={textContent} />;
|
||||||
<div
|
|
||||||
className="email-html-content mb-8 overflow-x-auto"
|
|
||||||
dangerouslySetInnerHTML={{ __html: textContent }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return (
|
return (
|
||||||
<div className="mb-8 p-6 bg-gray-50 rounded-xl border border-gray-100">
|
<div className="mb-8 p-6 bg-gray-50 rounded-xl border border-gray-100">
|
||||||
|
|
@ -646,26 +733,24 @@ const MailDetail = ({ onContinueDraft, onReply, onForward }) => {
|
||||||
const isHtml = /<[a-z][\s\S]*>/i.test(translatedContent);
|
const isHtml = /<[a-z][\s\S]*>/i.test(translatedContent);
|
||||||
return (
|
return (
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="absolute -top-2 left-4 px-3 py-1 rounded-full text-xs font-semibold text-white shadow-md flex items-center gap-1.5" style={{ background: 'linear-gradient(90deg, #4285f4, #ea4335, #fbbc05, #34a853)' }}>
|
<div className="absolute -top-2 left-4 px-3 py-1 rounded-full text-xs font-semibold text-white shadow-md flex items-center gap-1.5 z-10" style={{ background: 'linear-gradient(90deg, #4285f4, #ea4335, #fbbc05, #34a853)' }}>
|
||||||
<Sparkles className="w-3 h-3" />
|
<Sparkles className="w-3 h-3" />
|
||||||
번역됨
|
번역됨
|
||||||
</div>
|
</div>
|
||||||
{isHtml ? (
|
<div
|
||||||
<div
|
className="rounded-xl mt-2 overflow-hidden"
|
||||||
className="email-html-content mb-8 overflow-x-auto rounded-xl p-4 mt-2"
|
style={{ border: '2px solid transparent', background: 'linear-gradient(white, white) padding-box, linear-gradient(90deg, #4285f4, #ea4335, #fbbc05, #34a853) border-box' }}
|
||||||
style={{ border: '2px solid transparent', background: 'linear-gradient(white, white) padding-box, linear-gradient(90deg, #4285f4, #ea4335, #fbbc05, #34a853) border-box' }}
|
>
|
||||||
dangerouslySetInnerHTML={{ __html: translatedContent }}
|
{isHtml ? (
|
||||||
/>
|
<EmailIframe htmlContent={translatedContent} />
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div className="p-6">
|
||||||
className="mb-8 p-6 rounded-xl mt-2"
|
<pre className="whitespace-pre-wrap font-sans text-sm text-gray-700 leading-relaxed">
|
||||||
style={{ border: '2px solid transparent', background: 'linear-gradient(#f0f9ff, #f0f9ff) padding-box, linear-gradient(90deg, #4285f4, #ea4335, #fbbc05, #34a853) border-box' }}
|
{translatedContent}
|
||||||
>
|
</pre>
|
||||||
<pre className="whitespace-pre-wrap font-sans text-sm text-gray-700 leading-relaxed">
|
</div>
|
||||||
{translatedContent}
|
)}
|
||||||
</pre>
|
</div>
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue