// 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 = `
오류: ${results.error}
`; } else if (!Array.isArray(results) || results.length === 0) { html += `지식재산권이 없는 안전한 단어
`; } else { results.forEach((result, idx) => { html += `상표명: ${trademarkName} (불일치-확인필요)
`; } else { html += `상표명: ${trademarkName}
`; } html += `출원번호: ${dreg.applicationNum || "(출원번호 없음)"}
`; html += `출원날짜: ${dreg.applicationDate || "(출원날짜 없음)"}
`; html += `권리상태: ${dreg.lastDisposalCodeName || "(권리상태 없음)"}
`; html += `공고번호: ${dreg.publicationNum || "(공고번호 없음)"}
`; html += `등록번호: ${dreg.registerNum || "(등록번호 없음)"}
`; html += `카테고리 코드: ${key}
`; result.detail.rights_info[key].forEach(item => { html += `- 지정상품명: ${item.asignProductName || ""}
`; html += `영문: ${item.asignProductNameEn || ""}
`; html += `유사군코드: ${item.similarCodes || ""}
`; }); } } else { html += `(권리정보 없음)
`; } html += `국가명: ${mapping.nationalCodeName || "(없음)"}
`; html += `출원인명: ${mapping.applicantName || "(없음)"}
`; } else { html += `(출원인 정보 없음)
`; } } else if (result.detailError) { html += `상세 정보 검색 오류: ${result.detailError}
`; } html += `${originalText}
${result.translatedText}
` : `${result.error || '번역 실패'}
` }번역 결과가 없습니다.
다시 시도해 주세요.
💡 ${userLevel || 'Basic'} 회원으로 이용 중입니다. ${getUserLevelGuide(userLevel)}