AutoPercenty3/test/ext/wrmc_ext/content.js

867 lines
32 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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;">&nbsp;&nbsp;영문: ${item.asignProductNameEn || ""}</p>`;
html += `<p style="margin-left: 20px; margin: 4px 0;">&nbsp;&nbsp;유사군코드: ${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] 번역 툴팁 제거됨');
}
}