867 lines
32 KiB
JavaScript
867 lines
32 KiB
JavaScript
// content.js
|
||
|
||
let lastContextMenuPos = null;
|
||
let tooltipEl = null;
|
||
let currentKeyword = null; // 현재 검색 키워드 저장
|
||
let loadingIndicator = null; // 로딩 인디케이터 요소
|
||
|
||
// 마우스 위치 추적
|
||
let currentMousePos = { x: 0, y: 0 };
|
||
document.addEventListener('mousemove', (e) => {
|
||
currentMousePos = { x: e.pageX, y: e.pageY };
|
||
});
|
||
|
||
document.addEventListener("contextmenu", (e) => {
|
||
lastContextMenuPos = { x: e.pageX, y: e.pageY };
|
||
});
|
||
|
||
// ESC 키로 모달 닫기
|
||
document.addEventListener("keydown", (e) => {
|
||
if (e.key === "Escape") {
|
||
if (tooltipEl) removeTooltip();
|
||
if (loadingIndicator) removeLoadingIndicator();
|
||
}
|
||
});
|
||
|
||
// 로딩 인디케이터 생성 및 표시
|
||
function showLoadingIndicator(message, position = null) {
|
||
// 기존 로딩 인디케이터가 있으면 제거
|
||
if (loadingIndicator) {
|
||
removeLoadingIndicator();
|
||
}
|
||
|
||
loadingIndicator = document.createElement("div");
|
||
loadingIndicator.id = "markinfo-loading";
|
||
loadingIndicator.style.cssText = `
|
||
position: fixed;
|
||
z-index: 9999999;
|
||
background: rgba(0, 0, 0, 0.8);
|
||
color: white;
|
||
padding: 12px 20px;
|
||
border-radius: 8px;
|
||
font-family: 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||
font-size: 14px;
|
||
font-weight: 500;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
animation: fadeIn 0.3s ease-out;
|
||
backdrop-filter: blur(4px);
|
||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||
`;
|
||
|
||
// 스피너 아이콘
|
||
const spinner = document.createElement("div");
|
||
spinner.style.cssText = `
|
||
width: 16px;
|
||
height: 16px;
|
||
border: 2px solid rgba(255, 255, 255, 0.3);
|
||
border-top: 2px solid white;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
`;
|
||
|
||
// 메시지 텍스트
|
||
const messageEl = document.createElement("span");
|
||
messageEl.textContent = message;
|
||
|
||
loadingIndicator.appendChild(spinner);
|
||
loadingIndicator.appendChild(messageEl);
|
||
|
||
// CSS 애니메이션 정의
|
||
if (!document.getElementById('markinfo-loading-styles')) {
|
||
const style = document.createElement('style');
|
||
style.id = 'markinfo-loading-styles';
|
||
style.textContent = `
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
@keyframes fadeIn {
|
||
0% { opacity: 0; transform: translateY(-10px); }
|
||
100% { opacity: 1; transform: translateY(0); }
|
||
}
|
||
@keyframes fadeOut {
|
||
0% { opacity: 1; transform: translateY(0); }
|
||
100% { opacity: 0; transform: translateY(-10px); }
|
||
}
|
||
`;
|
||
document.head.appendChild(style);
|
||
}
|
||
|
||
document.body.appendChild(loadingIndicator);
|
||
|
||
// 위치 설정
|
||
const pos = position || getSelectionPosition() || currentMousePos;
|
||
positionLoadingIndicator(loadingIndicator, pos);
|
||
|
||
console.log('[content.js] 로딩 인디케이터 표시:', message);
|
||
}
|
||
|
||
// 로딩 인디케이터 제거
|
||
function removeLoadingIndicator() {
|
||
if (loadingIndicator) {
|
||
loadingIndicator.style.animation = 'fadeOut 0.3s ease-out';
|
||
setTimeout(() => {
|
||
if (loadingIndicator && loadingIndicator.parentNode) {
|
||
loadingIndicator.parentNode.removeChild(loadingIndicator);
|
||
}
|
||
loadingIndicator = null;
|
||
}, 300);
|
||
console.log('[content.js] 로딩 인디케이터 제거');
|
||
}
|
||
}
|
||
|
||
// 선택된 텍스트의 위치 가져오기
|
||
function getSelectionPosition() {
|
||
const selection = window.getSelection();
|
||
if (selection.rangeCount > 0) {
|
||
const range = selection.getRangeAt(0);
|
||
const rect = range.getBoundingClientRect();
|
||
if (rect.width > 0 && rect.height > 0) {
|
||
return {
|
||
x: rect.left + window.pageXOffset + rect.width / 2,
|
||
y: rect.top + window.pageYOffset - 10
|
||
};
|
||
}
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// 로딩 인디케이터 위치 설정
|
||
function positionLoadingIndicator(indicator, pos) {
|
||
indicator.style.left = (pos.x - 50) + "px"; // 중앙 정렬을 위해 조정
|
||
indicator.style.top = (pos.y - 50) + "px";
|
||
|
||
// 화면 경계 체크
|
||
const rect = indicator.getBoundingClientRect();
|
||
const docWidth = document.documentElement.clientWidth;
|
||
const docHeight = document.documentElement.clientHeight;
|
||
|
||
if (rect.right > docWidth) {
|
||
indicator.style.left = (docWidth - rect.width - 10) + "px";
|
||
}
|
||
if (rect.left < 0) {
|
||
indicator.style.left = "10px";
|
||
}
|
||
if (rect.bottom > docHeight) {
|
||
indicator.style.top = (docHeight - rect.height - 10) + "px";
|
||
}
|
||
if (rect.top < 0) {
|
||
indicator.style.top = "10px";
|
||
}
|
||
}
|
||
|
||
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
||
console.log('[content.js] 메시지 수신:', message);
|
||
|
||
// 핑 테스트 응답
|
||
if (message.action === "ping") {
|
||
console.log('[content.js] 핑 메시지 수신, 응답 전송');
|
||
sendResponse({ status: "ready" });
|
||
return true;
|
||
}
|
||
|
||
// 로딩 인디케이터 표시 요청
|
||
if (message.action === "showLoading") {
|
||
showLoadingIndicator(message.message, message.position);
|
||
sendResponse({ success: true });
|
||
return true;
|
||
}
|
||
|
||
// 로딩 인디케이터 제거 요청
|
||
if (message.action === "hideLoading") {
|
||
removeLoadingIndicator();
|
||
sendResponse({ success: true });
|
||
return true;
|
||
}
|
||
|
||
if (message.action === "showTooltip") {
|
||
console.log(`[content.js] showTooltip 메시지 수신, 키워드: ${message.keyword}, 결과 개수: ${message.detailInfo?.length || 0}`);
|
||
|
||
// 로딩 인디케이터 제거
|
||
removeLoadingIndicator();
|
||
|
||
// 현재 키워드 저장
|
||
currentKeyword = message.keyword;
|
||
|
||
try {
|
||
if (!tooltipEl) {
|
||
tooltipEl = document.createElement("div");
|
||
tooltipEl.id = "markinfo-tooltip";
|
||
tooltipEl.style.position = "absolute";
|
||
tooltipEl.style.zIndex = "999999";
|
||
tooltipEl.style.background = "#fff";
|
||
tooltipEl.style.border = "1px solid #ccc";
|
||
tooltipEl.style.borderRadius = "8px";
|
||
tooltipEl.style.boxShadow = "0 4px 12px rgba(0,0,0,0.15)";
|
||
tooltipEl.style.fontFamily = "'Roboto', sans-serif";
|
||
tooltipEl.style.fontSize = "14px";
|
||
tooltipEl.style.color = "#333";
|
||
tooltipEl.style.maxWidth = "600px";
|
||
tooltipEl.style.maxHeight = "500px";
|
||
// flex 컬럼 레이아웃으로 구성
|
||
tooltipEl.style.display = "flex";
|
||
tooltipEl.style.flexDirection = "column";
|
||
|
||
// 헤더: 항상 보이는 영역 (sticky)
|
||
const headerDiv = document.createElement("div");
|
||
headerDiv.id = "markinfo-tooltip-header";
|
||
headerDiv.style.position = "sticky";
|
||
headerDiv.style.top = "0";
|
||
headerDiv.style.background = "#fff";
|
||
headerDiv.style.padding = "12px 16px";
|
||
headerDiv.style.borderBottom = "1px solid #ccc";
|
||
headerDiv.style.display = "flex";
|
||
headerDiv.style.justifyContent = "space-between";
|
||
headerDiv.style.alignItems = "center";
|
||
|
||
// 헤더 내부: 검색 키워드와 제작자 정보를 수직 정렬
|
||
const headerContent = document.createElement("div");
|
||
headerContent.style.display = "flex";
|
||
headerContent.style.flexDirection = "column";
|
||
|
||
// 검색 키워드 제목
|
||
const titleElem = document.createElement("h2");
|
||
titleElem.id = "tooltip-title";
|
||
titleElem.style.margin = "0";
|
||
titleElem.style.fontSize = "20px";
|
||
titleElem.style.color = "#2c3e50";
|
||
headerContent.appendChild(titleElem);
|
||
|
||
// 제작자 정보 (작은 글씨)
|
||
const creatorElem = document.createElement("span");
|
||
creatorElem.id = "tooltip-creator";
|
||
creatorElem.textContent = "내차는언제타냐: 지재권 검색기 (ESC키로 닫기)";
|
||
creatorElem.style.fontSize = "12px";
|
||
creatorElem.style.color = "#7f8c8d";
|
||
headerContent.appendChild(creatorElem);
|
||
|
||
headerDiv.appendChild(headerContent);
|
||
|
||
// 헤더 버튼 영역
|
||
const headerButtons = document.createElement("div");
|
||
headerButtons.style.display = "flex";
|
||
headerButtons.style.gap = "8px";
|
||
|
||
// 금지어 추가 버튼 (헤더)
|
||
const addBannedBtn = document.createElement("button");
|
||
addBannedBtn.id = "add-banned-word-btn";
|
||
addBannedBtn.textContent = "내 금지어에 추가";
|
||
addBannedBtn.style.padding = "6px 12px";
|
||
addBannedBtn.style.backgroundColor = "#f39c12";
|
||
addBannedBtn.style.color = "#fff";
|
||
addBannedBtn.style.border = "none";
|
||
addBannedBtn.style.borderRadius = "4px";
|
||
addBannedBtn.style.cursor = "pointer";
|
||
addBannedBtn.style.fontSize = "12px";
|
||
addBannedBtn.onclick = () => addToBannedWords(currentKeyword);
|
||
headerButtons.appendChild(addBannedBtn);
|
||
|
||
// 내부 닫기 버튼 (헤더 우측)
|
||
const headerCloseBtn = document.createElement("button");
|
||
headerCloseBtn.textContent = "닫기";
|
||
headerCloseBtn.style.padding = "6px 10px";
|
||
headerCloseBtn.style.backgroundColor = "#e74c3c";
|
||
headerCloseBtn.style.color = "#fff";
|
||
headerCloseBtn.style.border = "none";
|
||
headerCloseBtn.style.borderRadius = "4px";
|
||
headerCloseBtn.style.cursor = "pointer";
|
||
headerCloseBtn.onclick = removeTooltip;
|
||
headerButtons.appendChild(headerCloseBtn);
|
||
|
||
headerDiv.appendChild(headerButtons);
|
||
|
||
// 본문 영역 (스크롤 가능)
|
||
const bodyDiv = document.createElement("div");
|
||
bodyDiv.id = "markinfo-tooltip-body";
|
||
bodyDiv.style.padding = "16px";
|
||
bodyDiv.style.overflowY = "auto";
|
||
bodyDiv.style.flex = "1 1 auto";
|
||
|
||
tooltipEl.appendChild(headerDiv);
|
||
tooltipEl.appendChild(bodyDiv);
|
||
|
||
document.body.appendChild(tooltipEl);
|
||
|
||
// 글로벌 닫기 버튼 (항상 보이는 우측 상단)
|
||
ensureGlobalCloseButton();
|
||
}
|
||
|
||
// 업데이트: 헤더 제목에 검색 키워드 설정
|
||
document.getElementById("tooltip-title").textContent = "검색 키워드: " + message.keyword;
|
||
renderDetailInfo(message.detailInfo, message.keyword);
|
||
|
||
if (lastContextMenuPos) {
|
||
positionTooltip(tooltipEl, lastContextMenuPos);
|
||
} else {
|
||
tooltipEl.style.top = "10px";
|
||
tooltipEl.style.left = "10px";
|
||
}
|
||
|
||
console.log('[content.js] 툴팁 표시 완료');
|
||
|
||
// 성공 응답 전송
|
||
sendResponse({ success: true, message: "툴팁이 성공적으로 표시되었습니다." });
|
||
|
||
} catch (error) {
|
||
console.error('[content.js] 툴팁 표시 중 오류:', error);
|
||
|
||
// 오류 응답 전송
|
||
sendResponse({ success: false, error: error.message });
|
||
}
|
||
|
||
// 비동기 응답을 위해 true 반환
|
||
return true;
|
||
}
|
||
|
||
// 멀티번역 결과 표시
|
||
if (message.action === "showTranslationTooltip") {
|
||
console.log(`[content.js] showTranslationTooltip 메시지 수신, 원문: ${message.originalText}, 결과 개수: ${message.results?.length || 0}`);
|
||
|
||
// 로딩 인디케이터 제거
|
||
removeLoadingIndicator();
|
||
|
||
try {
|
||
showTranslationResults(message.originalText, message.results, message.userLevel);
|
||
|
||
// 성공 응답 전송
|
||
sendResponse({ success: true, message: "번역 결과가 성공적으로 표시되었습니다." });
|
||
|
||
} catch (error) {
|
||
console.error('[content.js] 번역 결과 표시 중 오류:', error);
|
||
|
||
// 오류 응답 전송
|
||
sendResponse({ success: false, error: error.message });
|
||
}
|
||
|
||
// 비동기 응답을 위해 true 반환
|
||
return true;
|
||
}
|
||
});
|
||
|
||
function positionTooltip(tooltip, pos) {
|
||
tooltip.style.left = (pos.x + 10) + "px";
|
||
tooltip.style.top = (pos.y + 10) + "px";
|
||
const rect = tooltip.getBoundingClientRect();
|
||
const docWidth = document.documentElement.clientWidth;
|
||
const docHeight = document.documentElement.clientHeight;
|
||
if (rect.right > docWidth) {
|
||
tooltip.style.left = (docWidth - rect.width - 10) + "px";
|
||
}
|
||
if (rect.bottom > docHeight) {
|
||
tooltip.style.top = (docHeight - rect.height - 10) + "px";
|
||
}
|
||
}
|
||
|
||
function removeTooltip() {
|
||
if (tooltipEl && tooltipEl.parentNode) {
|
||
tooltipEl.parentNode.removeChild(tooltipEl);
|
||
}
|
||
tooltipEl = null;
|
||
const globalClose = document.getElementById("tooltip-global-close");
|
||
if (globalClose && globalClose.parentNode) {
|
||
globalClose.parentNode.removeChild(globalClose);
|
||
}
|
||
}
|
||
|
||
function ensureGlobalCloseButton() {
|
||
if (!document.getElementById("tooltip-global-close")) {
|
||
const btn = document.createElement("button");
|
||
btn.id = "tooltip-global-close";
|
||
btn.textContent = "닫기";
|
||
btn.style.position = "fixed";
|
||
btn.style.top = "20px";
|
||
btn.style.right = "20px";
|
||
btn.style.padding = "8px 12px";
|
||
btn.style.backgroundColor = "#e74c3c";
|
||
btn.style.color = "#fff";
|
||
btn.style.border = "none";
|
||
btn.style.borderRadius = "4px";
|
||
btn.style.cursor = "pointer";
|
||
btn.style.zIndex = "1000000";
|
||
btn.onclick = removeTooltip;
|
||
document.body.appendChild(btn);
|
||
}
|
||
}
|
||
|
||
function renderDetailInfo(results, keyword) {
|
||
// 검색 결과를 전역 변수에 저장
|
||
window.currentSearchResults = results;
|
||
|
||
const bodyDiv = document.getElementById("markinfo-tooltip-body");
|
||
if (!bodyDiv) return;
|
||
let html = `<div style="line-height: 1.6;">`;
|
||
// 결과가 없을 경우 안전한 단어 표시
|
||
if (results.error) {
|
||
html += `<p style="color: red; font-weight: bold;">오류: ${results.error}</p>`;
|
||
} else if (!Array.isArray(results) || results.length === 0) {
|
||
html += `<p style="color: #7f8c8d; font-style: italic;">지식재산권이 없는 안전한 단어</p>`;
|
||
} else {
|
||
results.forEach((result, idx) => {
|
||
html += `<div style="margin-bottom: 24px; padding: 16px; border: 1px solid #ecf0f1; border-radius: 8px; background-color: #fafafa;">`;
|
||
|
||
// 결과 헤더 (제목과 개별 금지어 추가 버튼)
|
||
html += `<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px;">`;
|
||
html += `<h3 style="margin: 0; font-size: 18px; color: #2980b9;">${idx + 1}번 결과</h3>`;
|
||
// html += `<button onclick="addIndividualToBannedWords('${result.registration_info?.trademarkName || keyword}', ${idx})"
|
||
// id="individual-banned-btn-${idx}"
|
||
// style="padding: 4px 8px; background-color: #e67e22; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 11px;">
|
||
// 이 상표를 금지어에 추가
|
||
// </button>`;
|
||
html += `</div>`;
|
||
|
||
// 상세 검색 결과 (출원번호 상세 조회)
|
||
if (result.detail) {
|
||
html += `<h4 style="margin: 12px 0 6px; font-size: 16px; color: #16a085; border-bottom: 1px solid #16a085; display: inline-block;">상세 정보</h4>`;
|
||
const dreg = result.detail.registration_info;
|
||
|
||
// 상표명 불일치 검사 및 표시
|
||
const trademarkName = dreg.trademarkName || "(상표명 없음)";
|
||
const isNameMismatch = trademarkName !== "(상표명 없음)" &&
|
||
keyword &&
|
||
trademarkName.toLowerCase() !== keyword.toLowerCase() &&
|
||
!trademarkName.toLowerCase().includes(keyword.toLowerCase()) &&
|
||
!keyword.toLowerCase().includes(trademarkName.toLowerCase());
|
||
|
||
if (isNameMismatch) {
|
||
html += `<p style="margin: 6px 0;"><strong>상표명:</strong> <span style="color: red; font-weight: bold;">${trademarkName} (불일치-확인필요)</span></p>`;
|
||
} else {
|
||
html += `<p style="margin: 6px 0;"><strong>상표명:</strong> ${trademarkName}</p>`;
|
||
}
|
||
|
||
html += `<p style="margin: 6px 0;"><strong>출원번호:</strong> ${dreg.applicationNum || "(출원번호 없음)"}</p>`;
|
||
html += `<p style="margin: 6px 0;"><strong>출원날짜:</strong> ${dreg.applicationDate || "(출원날짜 없음)"}</p>`;
|
||
html += `<p style="margin: 6px 0;"><strong>권리상태:</strong> ${dreg.lastDisposalCodeName || "(권리상태 없음)"}</p>`;
|
||
html += `<p style="margin: 6px 0;"><strong>공고번호:</strong> ${dreg.publicationNum || "(공고번호 없음)"}</p>`;
|
||
html += `<p style="margin: 6px 0;"><strong>등록번호:</strong> ${dreg.registerNum || "(등록번호 없음)"}</p>`;
|
||
|
||
html += `<h4 style="margin: 12px 0 6px; font-size: 16px; color: #8e44ad; border-bottom: 1px solid #8e44ad; display: inline-block;">권리정보</h4>`;
|
||
if (result.detail.rights_info && Object.keys(result.detail.rights_info).length > 0) {
|
||
for (let key in result.detail.rights_info) {
|
||
html += `<p style="margin: 6px 0;"><strong>카테고리 코드:</strong> ${key}</p>`;
|
||
result.detail.rights_info[key].forEach(item => {
|
||
html += `<p style="margin-left: 20px; margin: 4px 0;">- 지정상품명: ${item.asignProductName || ""}</p>`;
|
||
html += `<p style="margin-left: 20px; margin: 4px 0;"> 영문: ${item.asignProductNameEn || ""}</p>`;
|
||
html += `<p style="margin-left: 20px; margin: 4px 0;"> 유사군코드: ${item.similarCodes || ""}</p>`;
|
||
});
|
||
}
|
||
} else {
|
||
html += `<p style="margin: 6px 0; color: #7f8c8d;">(권리정보 없음)</p>`;
|
||
}
|
||
|
||
html += `<h4 style="margin: 12px 0 6px; font-size: 16px; color: #e67e22; border-bottom: 1px solid #e67e22; display: inline-block;">출원인 정보</h4>`;
|
||
if (result.detail.applicant_info && result.detail.applicant_info.mapping) {
|
||
const mapping = result.detail.applicant_info.mapping;
|
||
html += `<p style="margin: 6px 0;"><strong>국가명:</strong> ${mapping.nationalCodeName || "(없음)"}</p>`;
|
||
html += `<p style="margin: 6px 0;"><strong>출원인명:</strong> ${mapping.applicantName || "(없음)"}</p>`;
|
||
} else {
|
||
html += `<p style="margin: 6px 0; color: #7f8c8d;">(출원인 정보 없음)</p>`;
|
||
}
|
||
} else if (result.detailError) {
|
||
html += `<p style="color: red; font-weight: bold;">상세 정보 검색 오류: ${result.detailError}</p>`;
|
||
}
|
||
html += `</div>`;
|
||
});
|
||
}
|
||
html += `</div>`;
|
||
bodyDiv.innerHTML = html;
|
||
}
|
||
|
||
// 금지어 추가 함수
|
||
async function addToBannedWords(keyword) {
|
||
try {
|
||
console.log(`[content.js] 금지어 추가 시작: ${keyword}`);
|
||
|
||
// Grade 선택 모달 표시
|
||
const selectedGrade = await showGradeSelectionModal(keyword);
|
||
if (!selectedGrade) {
|
||
console.log('[content.js] 사용자가 금지어 추가를 취소했습니다.');
|
||
return;
|
||
}
|
||
|
||
// 백그라운드 스크립트에 금지어 추가 요청
|
||
chrome.runtime.sendMessage({
|
||
action: "addBannedWord",
|
||
keyword: keyword,
|
||
grade: selectedGrade,
|
||
searchResults: getCurrentSearchResults()
|
||
}, (response) => {
|
||
if (chrome.runtime.lastError) {
|
||
console.error('[content.js] 금지어 추가 메시지 전송 실패:', chrome.runtime.lastError);
|
||
alert('금지어 추가 중 오류가 발생했습니다.');
|
||
return;
|
||
}
|
||
|
||
if (response && response.success) {
|
||
console.log('[content.js] 금지어 추가 성공');
|
||
alert(`"${keyword}"이(가) 금지어 목록에 추가되었습니다. (등급: ${selectedGrade})`);
|
||
|
||
// 금지어 추가 버튼 비활성화
|
||
const addBtn = document.getElementById("add-banned-word-btn");
|
||
if (addBtn) {
|
||
addBtn.textContent = "추가 완료";
|
||
addBtn.disabled = true;
|
||
addBtn.style.backgroundColor = "#95a5a6";
|
||
addBtn.style.cursor = "not-allowed";
|
||
}
|
||
} else {
|
||
console.error('[content.js] 금지어 추가 실패:', response?.error);
|
||
alert(`금지어 추가 실패: ${response?.error || '알 수 없는 오류'}`);
|
||
}
|
||
});
|
||
|
||
} catch (error) {
|
||
console.error('[content.js] 금지어 추가 중 오류:', error);
|
||
alert('금지어 추가 중 오류가 발생했습니다.');
|
||
}
|
||
}
|
||
|
||
// Grade 선택 모달 함수
|
||
function showGradeSelectionModal(keyword) {
|
||
return new Promise((resolve) => {
|
||
// 모달 배경
|
||
const modalOverlay = document.createElement('div');
|
||
modalOverlay.style.position = 'fixed';
|
||
modalOverlay.style.top = '0';
|
||
modalOverlay.style.left = '0';
|
||
modalOverlay.style.width = '100%';
|
||
modalOverlay.style.height = '100%';
|
||
modalOverlay.style.backgroundColor = 'rgba(0, 0, 0, 0.5)';
|
||
modalOverlay.style.zIndex = '1000000';
|
||
modalOverlay.style.display = 'flex';
|
||
modalOverlay.style.justifyContent = 'center';
|
||
modalOverlay.style.alignItems = 'center';
|
||
|
||
// 모달 컨테이너
|
||
const modalContainer = document.createElement('div');
|
||
modalContainer.style.backgroundColor = '#fff';
|
||
modalContainer.style.borderRadius = '8px';
|
||
modalContainer.style.padding = '24px';
|
||
modalContainer.style.maxWidth = '400px';
|
||
modalContainer.style.width = '90%';
|
||
modalContainer.style.boxShadow = '0 4px 20px rgba(0, 0, 0, 0.3)';
|
||
modalContainer.style.fontFamily = "'Roboto', sans-serif";
|
||
|
||
// 제목
|
||
const title = document.createElement('h3');
|
||
title.textContent = '금지어 등급 선택';
|
||
title.style.margin = '0 0 16px 0';
|
||
title.style.color = '#2c3e50';
|
||
title.style.fontSize = '18px';
|
||
modalContainer.appendChild(title);
|
||
|
||
// 키워드 표시
|
||
const keywordLabel = document.createElement('p');
|
||
keywordLabel.textContent = `키워드: "${keyword}"`;
|
||
keywordLabel.style.margin = '0 0 16px 0';
|
||
keywordLabel.style.color = '#7f8c8d';
|
||
keywordLabel.style.fontSize = '14px';
|
||
modalContainer.appendChild(keywordLabel);
|
||
|
||
// 설명
|
||
const description = document.createElement('p');
|
||
description.textContent = '이 키워드의 금지 등급을 선택해주세요:';
|
||
description.style.margin = '0 0 12px 0';
|
||
description.style.color = '#34495e';
|
||
description.style.fontSize = '14px';
|
||
modalContainer.appendChild(description);
|
||
|
||
// Grade 선택 드롭박스
|
||
const gradeSelect = document.createElement('select');
|
||
gradeSelect.style.width = '100%';
|
||
gradeSelect.style.padding = '8px 12px';
|
||
gradeSelect.style.border = '1px solid #bdc3c7';
|
||
gradeSelect.style.borderRadius = '4px';
|
||
gradeSelect.style.fontSize = '14px';
|
||
gradeSelect.style.marginBottom = '20px';
|
||
|
||
// 옵션 추가
|
||
const gradeOptions = [
|
||
{ value: '비허용', text: '비허용(단어제거)' },
|
||
{ value: '금지', text: '금지(상품금지)' }
|
||
];
|
||
|
||
gradeOptions.forEach(option => {
|
||
const optionElement = document.createElement('option');
|
||
optionElement.value = option.value;
|
||
optionElement.textContent = option.text;
|
||
if (option.value === '비허용') {
|
||
optionElement.selected = true; // 기본값: 비허용
|
||
}
|
||
gradeSelect.appendChild(optionElement);
|
||
});
|
||
|
||
modalContainer.appendChild(gradeSelect);
|
||
|
||
// 버튼 컨테이너
|
||
const buttonContainer = document.createElement('div');
|
||
buttonContainer.style.display = 'flex';
|
||
buttonContainer.style.justifyContent = 'flex-end';
|
||
buttonContainer.style.gap = '8px';
|
||
|
||
// 취소 버튼
|
||
const cancelButton = document.createElement('button');
|
||
cancelButton.textContent = '취소';
|
||
cancelButton.style.padding = '8px 16px';
|
||
cancelButton.style.backgroundColor = '#95a5a6';
|
||
cancelButton.style.color = '#fff';
|
||
cancelButton.style.border = 'none';
|
||
cancelButton.style.borderRadius = '4px';
|
||
cancelButton.style.cursor = 'pointer';
|
||
cancelButton.style.fontSize = '14px';
|
||
|
||
cancelButton.onclick = () => {
|
||
document.body.removeChild(modalOverlay);
|
||
resolve(null); // 취소
|
||
};
|
||
|
||
// 확인 버튼
|
||
const confirmButton = document.createElement('button');
|
||
confirmButton.textContent = '추가';
|
||
confirmButton.style.padding = '8px 16px';
|
||
confirmButton.style.backgroundColor = '#f39c12';
|
||
confirmButton.style.color = '#fff';
|
||
confirmButton.style.border = 'none';
|
||
confirmButton.style.borderRadius = '4px';
|
||
confirmButton.style.cursor = 'pointer';
|
||
confirmButton.style.fontSize = '14px';
|
||
|
||
confirmButton.onclick = () => {
|
||
const selectedGrade = gradeSelect.value;
|
||
document.body.removeChild(modalOverlay);
|
||
resolve(selectedGrade);
|
||
};
|
||
|
||
buttonContainer.appendChild(cancelButton);
|
||
buttonContainer.appendChild(confirmButton);
|
||
modalContainer.appendChild(buttonContainer);
|
||
|
||
// ESC 키로 닫기
|
||
const handleKeyDown = (e) => {
|
||
if (e.key === 'Escape') {
|
||
document.body.removeChild(modalOverlay);
|
||
document.removeEventListener('keydown', handleKeyDown);
|
||
resolve(null);
|
||
}
|
||
};
|
||
|
||
document.addEventListener('keydown', handleKeyDown);
|
||
|
||
modalOverlay.appendChild(modalContainer);
|
||
document.body.appendChild(modalOverlay);
|
||
|
||
// 드롭박스에 포커스
|
||
gradeSelect.focus();
|
||
});
|
||
}
|
||
|
||
// 현재 검색 결과 데이터 가져오기
|
||
function getCurrentSearchResults() {
|
||
// 현재 표시된 검색 결과 데이터를 반환
|
||
// 이 데이터는 renderDetailInfo에서 사용된 results와 동일해야 함
|
||
return window.currentSearchResults || [];
|
||
}
|
||
|
||
// 번역 결과 모달 표시
|
||
function showTranslationResults(originalText, results, userLevel) {
|
||
console.log('[content.js] 번역 결과 표시 시작');
|
||
|
||
let translationTooltip = document.getElementById("translation-tooltip");
|
||
|
||
if (!translationTooltip) {
|
||
translationTooltip = document.createElement("div");
|
||
translationTooltip.id = "translation-tooltip";
|
||
translationTooltip.style.position = "fixed";
|
||
translationTooltip.style.zIndex = "9999999";
|
||
translationTooltip.style.background = "#fff";
|
||
translationTooltip.style.border = "2px solid #3498db";
|
||
translationTooltip.style.borderRadius = "12px";
|
||
translationTooltip.style.boxShadow = "0 8px 24px rgba(0,0,0,0.2)";
|
||
translationTooltip.style.fontFamily = "'Roboto', sans-serif";
|
||
translationTooltip.style.fontSize = "14px";
|
||
translationTooltip.style.color = "#333";
|
||
translationTooltip.style.minWidth = "400px";
|
||
translationTooltip.style.maxWidth = "800px";
|
||
translationTooltip.style.maxHeight = "600px";
|
||
translationTooltip.style.display = "flex";
|
||
translationTooltip.style.flexDirection = "column";
|
||
|
||
// 헤더
|
||
const header = document.createElement("div");
|
||
header.style.background = "linear-gradient(135deg, #3498db, #2980b9)";
|
||
header.style.color = "#fff";
|
||
header.style.padding = "16px 20px";
|
||
header.style.borderRadius = "10px 10px 0 0";
|
||
header.style.display = "flex";
|
||
header.style.justifyContent = "space-between";
|
||
header.style.alignItems = "center";
|
||
|
||
// 헤더 내용
|
||
const headerContent = document.createElement("div");
|
||
|
||
const titleElem = document.createElement("h2");
|
||
titleElem.style.margin = "0";
|
||
titleElem.style.fontSize = "18px";
|
||
titleElem.textContent = "멀티번역 결과";
|
||
headerContent.appendChild(titleElem);
|
||
|
||
const subtitleElem = document.createElement("div");
|
||
subtitleElem.style.fontSize = "12px";
|
||
subtitleElem.style.opacity = "0.9";
|
||
subtitleElem.style.marginTop = "4px";
|
||
subtitleElem.textContent = `회원등급: ${userLevel || 'Basic'} | ESC키로 닫기`;
|
||
headerContent.appendChild(subtitleElem);
|
||
|
||
header.appendChild(headerContent);
|
||
|
||
// 닫기 버튼
|
||
const closeBtn = document.createElement("button");
|
||
closeBtn.textContent = "×";
|
||
closeBtn.style.background = "none";
|
||
closeBtn.style.border = "2px solid #fff";
|
||
closeBtn.style.color = "#fff";
|
||
closeBtn.style.borderRadius = "50%";
|
||
closeBtn.style.width = "30px";
|
||
closeBtn.style.height = "30px";
|
||
closeBtn.style.cursor = "pointer";
|
||
closeBtn.style.fontSize = "18px";
|
||
closeBtn.style.lineHeight = "1";
|
||
closeBtn.onclick = removeTranslationTooltip;
|
||
header.appendChild(closeBtn);
|
||
|
||
// 본문
|
||
const body = document.createElement("div");
|
||
body.id = "translation-body";
|
||
body.style.padding = "20px";
|
||
body.style.overflowY = "auto";
|
||
body.style.flex = "1 1 auto";
|
||
|
||
translationTooltip.appendChild(header);
|
||
translationTooltip.appendChild(body);
|
||
|
||
document.body.appendChild(translationTooltip);
|
||
|
||
// ESC 키로 닫기
|
||
document.addEventListener("keydown", function(e) {
|
||
if (e.key === "Escape") {
|
||
removeTranslationTooltip();
|
||
}
|
||
});
|
||
}
|
||
|
||
// 본문 업데이트
|
||
renderTranslationResults(originalText, results, userLevel);
|
||
|
||
// 위치 설정 (화면 중앙)
|
||
translationTooltip.style.top = "50%";
|
||
translationTooltip.style.left = "50%";
|
||
translationTooltip.style.transform = "translate(-50%, -50%)";
|
||
|
||
console.log('[content.js] 번역 결과 표시 완료');
|
||
}
|
||
|
||
// 번역 결과 렌더링
|
||
function renderTranslationResults(originalText, results, userLevel) {
|
||
const body = document.getElementById("translation-body");
|
||
if (!body) return;
|
||
|
||
let html = '';
|
||
|
||
// 원문 표시
|
||
html += `
|
||
<div style="margin-bottom: 20px; padding: 16px; background: #f8f9fa; border-radius: 8px; border-left: 4px solid #3498db;">
|
||
<h3 style="margin: 0 0 8px 0; color: #2c3e50; font-size: 16px;">원문</h3>
|
||
<p style="margin: 0; font-size: 15px; line-height: 1.5; color: #333;">${originalText}</p>
|
||
</div>
|
||
`;
|
||
|
||
// 번역 결과가 있을 경우
|
||
if (results && results.length > 0) {
|
||
html += '<div style="margin-bottom: 16px;">';
|
||
|
||
results.forEach((result, index) => {
|
||
const engineName = getEngineDisplayName(result.engine);
|
||
const isSuccess = result.success;
|
||
|
||
// 각 번역 결과 카드
|
||
html += `
|
||
<div style="margin-bottom: 16px; padding: 16px; border: 1px solid ${isSuccess ? '#e1e8ed' : '#f8d7da'}; border-radius: 8px; background: ${isSuccess ? '#fff' : '#f8f9fa'};">
|
||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: ${isSuccess ? '12px' : '8px'};">
|
||
<h4 style="margin: 0; color: ${isSuccess ? '#2c3e50' : '#721c24'}; font-size: 14px; font-weight: 600;">
|
||
${engineName}
|
||
</h4>
|
||
<span style="padding: 4px 8px; border-radius: 4px; font-size: 12px; background: ${isSuccess ? '#d4edda' : '#f8d7da'}; color: ${isSuccess ? '#155724' : '#721c24'};">
|
||
${isSuccess ? '성공' : '실패'}
|
||
</span>
|
||
</div>
|
||
|
||
${isSuccess ?
|
||
`<p style="margin: 0; font-size: 15px; line-height: 1.5; color: #333;">${result.translatedText}</p>` :
|
||
`<p style="margin: 0; font-size: 14px; color: #721c24;">${result.error || '번역 실패'}</p>`
|
||
}
|
||
</div>
|
||
`;
|
||
});
|
||
|
||
html += '</div>';
|
||
} else {
|
||
// 번역 결과가 없는 경우
|
||
html += `
|
||
<div style="text-align: center; padding: 40px; color: #6c757d;">
|
||
<p style="margin: 0; font-size: 16px;">번역 결과가 없습니다.</p>
|
||
<p style="margin: 8px 0 0 0; font-size: 14px;">다시 시도해 주세요.</p>
|
||
</div>
|
||
`;
|
||
}
|
||
|
||
// 회원등급별 가이드 메시지
|
||
html += `
|
||
<div style="margin-top: 20px; padding: 12px; background: #e3f2fd; border-radius: 6px; border-left: 4px solid #2196f3;">
|
||
<p style="margin: 0; font-size: 13px; color: #1565c0;">
|
||
💡 <strong>${userLevel || 'Basic'} 회원</strong>으로 이용 중입니다.
|
||
${getUserLevelGuide(userLevel)}
|
||
</p>
|
||
</div>
|
||
`;
|
||
|
||
body.innerHTML = html;
|
||
}
|
||
|
||
// 엔진 이름 표시용 변환
|
||
function getEngineDisplayName(engine) {
|
||
const displayNames = {
|
||
'google': '구글 번역',
|
||
'deepl': 'DeepL',
|
||
'openai': 'ChatGPT',
|
||
'gemini': 'Google Gemini',
|
||
'mymemory': 'MyMemory'
|
||
};
|
||
return displayNames[engine] || engine;
|
||
}
|
||
|
||
// 회원등급별 가이드 메시지
|
||
function getUserLevelGuide(userLevel) {
|
||
const normalizedLevel = (userLevel || 'basic').toLowerCase();
|
||
|
||
switch(normalizedLevel) {
|
||
case 'vip':
|
||
return '모든 번역 엔진을 이용할 수 있습니다!';
|
||
case 'premium':
|
||
return '무료 번역과 DeepL을 이용할 수 있습니다. VIP로 업그레이드하면 ChatGPT, Gemini도 이용 가능합니다.';
|
||
case 'basic':
|
||
default:
|
||
return '현재 무료 번역만 이용 가능합니다. 프리미엄 회원으로 업그레이드하면 더 많은 번역 엔진을 이용할 수 있습니다.';
|
||
}
|
||
}
|
||
|
||
// 번역 툴팁 제거
|
||
function removeTranslationTooltip() {
|
||
const translationTooltip = document.getElementById("translation-tooltip");
|
||
if (translationTooltip) {
|
||
translationTooltip.remove();
|
||
console.log('[content.js] 번역 툴팁 제거됨');
|
||
}
|
||
}
|