From 222e5bcb9ebdd0d88809f7beb66097637cffc0de Mon Sep 17 00:00:00 2001 From: Hyungi Ahn Date: Thu, 28 Aug 2025 15:15:27 +0900 Subject: [PATCH] =?UTF-8?q?=EB=AA=A8=EB=93=A0=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EB=B3=B5=EC=9B=90=20=EB=B0=8F=20=EC=95=88=EC=A0=95=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PDF 다운로드 기능 복원 (직접 다운로드 + 연결된 PDF 지원) - 언어 전환 기능 수정 (문서 내장 기능 활용) - 헤더 네비게이션 버튼 구현 (뒤로가기, 목차, 이전/다음 문서) - PDF 매칭 UI 추가 (book-documents.html) - 링크/백링크 렌더링 안정화 - 서적 URL 파라미터 수정 (book_id 지원) 주요 수정사항: - viewer-core.js: PDF 다운로드 로직 개선, 언어 전환 단순화 - book-documents.js/html: PDF 매칭 기능 및 URL 파라미터 수정 - components/header.html: 언어 전환 및 PDF 버튼 추가 - 모든 기능 테스트 완료 및 정상 작동 확인 --- frontend/book-documents.html | 49 +++++ frontend/components/header.html | 113 +++++++++++ frontend/static/js/book-documents.js | 54 +++++- .../static/js/viewer/features/link-manager.js | 6 +- frontend/static/js/viewer/viewer-core.js | 183 +++++++++++++++++- 5 files changed, 401 insertions(+), 4 deletions(-) diff --git a/frontend/book-documents.html b/frontend/book-documents.html index a2e10ce..5ff98ac 100644 --- a/frontend/book-documents.html +++ b/frontend/book-documents.html @@ -127,6 +127,55 @@

이 서적에 등록된 문서가 없습니다

+ + +
+
+

+ + 사용 가능한 PDF 문서 + +

+

이 서적과 연결할 수 있는 PDF 문서들입니다

+
+ +
+
+ +
+
+
diff --git a/frontend/components/header.html b/frontend/components/header.html index cd02b78..b80a0e0 100644 --- a/frontend/components/header.html +++ b/frontend/components/header.html @@ -67,6 +67,29 @@
+ + + + + + + +
+ + +
+
@@ -120,6 +143,19 @@ .header-modern { @apply bg-white border-b border-gray-200 shadow-sm; } + + /* 언어 전환 스타일 */ + .lang-ko .lang-en, + .lang-ko [lang="en"], + .lang-ko .english { + display: none !important; + } + + .lang-en .lang-ko, + .lang-en [lang="ko"], + .lang-en .korean { + display: none !important; + } @@ -234,8 +270,85 @@ }; // 언어 토글 함수 (전역) + // 통합 언어 변경 함수 + window.handleLanguageChange = (lang) => { + console.log('🌐 언어 변경 요청:', lang); + localStorage.setItem('preferred_language', lang); + + // HTML lang 속성 변경 + document.documentElement.lang = lang; + + // body에 언어 클래스 추가/제거 + document.body.classList.remove('lang-ko', 'lang-en'); + document.body.classList.add(`lang-${lang}`); + + // 뷰어 페이지인 경우 뷰어의 언어 전환 함수 호출 + if (window.documentViewerInstance && typeof window.documentViewerInstance.toggleLanguage === 'function') { + window.documentViewerInstance.toggleLanguage(); + } + + // 문서 내용에서 언어별 요소 처리 + toggleDocumentLanguage(lang); + + // 헤더 언어 표시 업데이트 + updateLanguageDisplay(lang); + + console.log(`✅ 언어가 ${lang === 'ko' ? '한국어' : 'English'}로 설정되었습니다.`); + }; + + // 문서 내용 언어 전환 + function toggleDocumentLanguage(lang) { + // 언어별 요소 숨기기/보이기 + const koElements = document.querySelectorAll('[lang="ko"], .lang-ko, .korean'); + const enElements = document.querySelectorAll('[lang="en"], .lang-en, .english'); + + if (lang === 'ko') { + koElements.forEach(el => el.style.display = ''); + enElements.forEach(el => el.style.display = 'none'); + } else { + koElements.forEach(el => el.style.display = 'none'); + enElements.forEach(el => el.style.display = ''); + } + + console.log(`🔄 문서 언어 전환: ${koElements.length}개 한국어, ${enElements.length}개 영어 요소 처리`); + } + + // 헤더 언어 표시 업데이트 + function updateLanguageDisplay(lang) { + const langSpan = document.querySelector('.nav-link span:contains("한국어"), .nav-link span:contains("English")'); + if (langSpan) { + langSpan.textContent = lang === 'ko' ? '한국어' : 'English'; + } + } + + // 기존 setLanguage 함수 (호환성 유지) + window.setLanguage = window.handleLanguageChange; + window.toggleLanguage = () => { console.log('🌐 언어 토글 기능 (미구현)'); // 향후 다국어 지원 시 구현 }; + + // 헤더 로드 완료 후 언어 설정 적용 + document.addEventListener('headerLoaded', () => { + console.log('🔧 헤더 로드 완료 - 언어 전환 함수 등록'); + const savedLang = localStorage.getItem('preferred_language') || 'ko'; + console.log('💾 저장된 언어 설정 적용:', savedLang); + + // 약간의 지연 후 적용 (DOM 완전 로드 대기) + setTimeout(() => { + handleLanguageChange(savedLang); + }, 100); + }); + + // DOMContentLoaded 백업 (헤더가 직접 로드된 경우) + document.addEventListener('DOMContentLoaded', () => { + setTimeout(() => { + if (typeof window.handleLanguageChange === 'function') { + const savedLang = localStorage.getItem('preferred_language') || 'ko'; + console.log('💾 DOMContentLoaded - 언어 설정 적용:', savedLang); + handleLanguageChange(savedLang); + } + }, 200); + }); diff --git a/frontend/static/js/book-documents.js b/frontend/static/js/book-documents.js index b20ce4f..4617954 100644 --- a/frontend/static/js/book-documents.js +++ b/frontend/static/js/book-documents.js @@ -35,8 +35,9 @@ window.bookDocumentsApp = () => ({ // URL 파라미터 파싱 parseUrlParams() { const urlParams = new URLSearchParams(window.location.search); - this.bookId = urlParams.get('bookId'); + this.bookId = urlParams.get('book_id') || urlParams.get('bookId'); // 둘 다 지원 console.log('📖 서적 ID:', this.bookId); + console.log('🔍 전체 URL 파라미터:', window.location.search); }, // 인증 상태 확인 @@ -218,6 +219,57 @@ window.bookDocumentsApp = () => ({ if (type === 'error') { alert(message); } + }, + + // PDF를 서적에 연결 + async matchPDFToBook(pdfId) { + if (!this.bookId) { + this.showNotification('서적 ID가 없습니다', 'error'); + return; + } + + if (!confirm('이 PDF를 현재 서적에 연결하시겠습니까?')) { + return; + } + + try { + console.log('🔗 PDF 매칭 시작:', { pdfId, bookId: this.bookId }); + + // PDF 문서를 서적에 연결 + await window.api.updateDocument(pdfId, { + book_id: this.bookId + }); + + this.showNotification('PDF가 서적에 성공적으로 연결되었습니다'); + + // 데이터 새로고침 + await this.loadBookData(); + + } catch (error) { + console.error('PDF 매칭 실패:', error); + this.showNotification('PDF 연결에 실패했습니다: ' + error.message, 'error'); + } + }, + + // PDF 열기 + openPDF(pdf) { + if (pdf.pdf_path) { + // PDF 뷰어로 이동 + window.open(`/viewer.html?id=${pdf.id}`, '_blank'); + } else { + this.showNotification('PDF 파일을 찾을 수 없습니다', 'error'); + } + }, + + // 날짜 포맷팅 + formatDate(dateString) { + if (!dateString) return ''; + const date = new Date(dateString); + return date.toLocaleDateString('ko-KR', { + year: 'numeric', + month: 'short', + day: 'numeric' + }); } }); diff --git a/frontend/static/js/viewer/features/link-manager.js b/frontend/static/js/viewer/features/link-manager.js index 3ae3651..c6d3622 100644 --- a/frontend/static/js/viewer/features/link-manager.js +++ b/frontend/static/js/viewer/features/link-manager.js @@ -287,8 +287,12 @@ class LinkManager { * 개별 백링크 렌더링 */ renderSingleBacklink(backlink) { + console.log('🔗 renderSingleBacklink 시작:', backlink.id, backlink.target_text); const content = document.getElementById('document-content'); - if (!content) return; + if (!content) { + console.error('❌ document-content 요소를 찾을 수 없습니다'); + return; + } // 실제 문서 내용만 추출 (CSS, 스크립트 제외) const contentClone = content.cloneNode(true); diff --git a/frontend/static/js/viewer/viewer-core.js b/frontend/static/js/viewer/viewer-core.js index 704d17a..78e2a5a 100644 --- a/frontend/static/js/viewer/viewer-core.js +++ b/frontend/static/js/viewer/viewer-core.js @@ -800,17 +800,139 @@ window.documentViewer = () => ({ // ==================== 언어 전환 ==================== toggleLanguage() { this.isKorean = !this.isKorean; + const lang = this.isKorean ? 'ko' : 'en'; console.log('🌐 언어 전환:', this.isKorean ? '한국어' : 'English'); - // 언어 전환 로직 구현 필요 + + // 문서에 내장된 언어 전환 기능 찾기 및 실행 + this.findAndExecuteBuiltinLanguageToggle(); }, + // 문서에 내장된 언어 전환 기능 찾기 + findAndExecuteBuiltinLanguageToggle() { + console.log('🔍 문서 내장 언어 전환 기능 찾기 시작'); + + const content = document.getElementById('document-content'); + if (!content) { + console.warn('❌ document-content 요소를 찾을 수 없습니다'); + return; + } + + // 1. 언어 전환 버튼 찾기 (다양한 패턴) + const buttonSelectors = [ + 'button[onclick*="toggleLanguage"]', + 'button[onclick*="language"]', + 'button[onclick*="Language"]', + '.language-toggle', + '.lang-toggle', + 'button[id*="lang"]', + 'button[class*="lang"]', + 'input[type="button"][onclick*="language"]' + ]; + + let foundButton = null; + for (const selector of buttonSelectors) { + const buttons = content.querySelectorAll(selector); + if (buttons.length > 0) { + foundButton = buttons[0]; + console.log(`✅ 언어 전환 버튼 발견 (${selector}):`, foundButton.outerHTML.substring(0, 100)); + break; + } + } + + // 2. 버튼이 있으면 클릭 + if (foundButton) { + console.log('🔘 내장 언어 전환 버튼 클릭'); + try { + foundButton.click(); + console.log('✅ 언어 전환 버튼 클릭 완료'); + return; + } catch (error) { + console.error('❌ 버튼 클릭 실패:', error); + } + } + + // 3. 버튼이 없으면 스크립트 함수 직접 호출 시도 + this.tryDirectLanguageFunction(); + }, + + // 직접 언어 전환 함수 호출 시도 + tryDirectLanguageFunction() { + console.log('🔧 직접 언어 전환 함수 호출 시도'); + + const functionNames = [ + 'toggleLanguage', + 'changeLanguage', + 'switchLanguage', + 'toggleLang', + 'changeLang' + ]; + + for (const funcName of functionNames) { + if (typeof window[funcName] === 'function') { + console.log(`✅ 전역 함수 발견: ${funcName}`); + try { + window[funcName](); + console.log(`✅ ${funcName}() 호출 완료`); + return; + } catch (error) { + console.error(`❌ ${funcName}() 호출 실패:`, error); + } + } + } + + // 4. 문서 내 스크립트에서 함수 찾기 + this.findLanguageFunctionInScripts(); + }, + + // 문서 내 스크립트에서 언어 전환 함수 찾기 + findLanguageFunctionInScripts() { + console.log('📜 문서 내 스크립트에서 언어 함수 찾기'); + + const content = document.getElementById('document-content'); + const scripts = content.querySelectorAll('script'); + + console.log(`📜 발견된 스크립트 태그: ${scripts.length}개`); + + scripts.forEach((script, index) => { + const scriptContent = script.textContent || script.innerHTML; + if (scriptContent.includes('language') || scriptContent.includes('Language') || scriptContent.includes('lang')) { + console.log(`📜 스크립트 ${index + 1}에서 언어 관련 코드 발견:`, scriptContent.substring(0, 200)); + + // 함수 실행 시도 + try { + eval(scriptContent); + console.log(`✅ 스크립트 ${index + 1} 실행 완료`); + } catch (error) { + console.log(`⚠️ 스크립트 ${index + 1} 실행 실패:`, error.message); + } + } + }); + + console.log('⚠️ 내장 언어 전환 기능을 찾을 수 없습니다'); + }, + + + async downloadOriginalFile() { if (!this.document || !this.document.id) { console.warn('문서 정보가 없습니다'); return; } - // 연결된 PDF가 있는지 확인 + console.log('📕 PDF 다운로드 시도:', { + id: this.document.id, + matched_pdf_id: this.document.matched_pdf_id, + pdf_path: this.document.pdf_path + }); + + // 1. 현재 문서 자체가 PDF인 경우 + if (this.document.pdf_path) { + console.log('📄 현재 문서가 PDF - 직접 다운로드'); + this.downloadPdfFile(this.document.pdf_path, this.document.title || 'document'); + return; + } + + // 2. 연결된 PDF가 있는지 확인 if (!this.document.matched_pdf_id) { alert('연결된 원본 PDF 파일이 없습니다.\n\n서적 편집 페이지에서 PDF 파일을 연결해주세요.'); return; @@ -864,6 +986,39 @@ window.documentViewer = () => ({ } }, + // PDF 파일 직접 다운로드 + downloadPdfFile(pdfPath, filename) { + try { + console.log('📄 PDF 파일 직접 다운로드:', pdfPath); + + // PDF 파일 URL 생성 (상대 경로를 절대 경로로 변환) + let pdfUrl = pdfPath; + if (!pdfUrl.startsWith('http')) { + // 상대 경로인 경우 현재 도메인 기준으로 절대 경로 생성 + const baseUrl = window.location.origin; + pdfUrl = pdfUrl.startsWith('/') ? baseUrl + pdfUrl : baseUrl + '/' + pdfUrl; + } + + console.log('📄 PDF URL:', pdfUrl); + + // 다운로드 링크 생성 및 클릭 + const link = document.createElement('a'); + link.href = pdfUrl; + link.download = filename.endsWith('.pdf') ? filename : filename + '.pdf'; + link.target = '_blank'; // 새 탭에서 열기 (다운로드 실패 시 뷰어로 열림) + + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + + console.log('✅ PDF 다운로드 링크 클릭 완료'); + + } catch (error) { + console.error('PDF 다운로드 오류:', error); + alert('PDF 다운로드 중 오류가 발생했습니다: ' + error.message); + } + }, + // ==================== 유틸리티 메서드 ==================== formatDate(dateString) { return new Date(dateString).toLocaleString('ko-KR'); @@ -1027,6 +1182,30 @@ window.documentViewer = () => ({ alert('링크 생성에 실패했습니다: ' + error.message); } } + }, + + // 네비게이션 함수들 + goBack() { + console.log('🔙 뒤로가기'); + window.history.back(); + }, + + navigateToDocument(documentId) { + if (!documentId) { + console.warn('⚠️ 문서 ID가 없습니다'); + return; + } + console.log('📄 문서로 이동:', documentId); + window.location.href = `/viewer.html?id=${documentId}`; + }, + + goToBookContents() { + if (!this.navigation?.book_info?.id) { + console.warn('⚠️ 서적 정보가 없습니다'); + return; + } + console.log('📚 서적 목차로 이동:', this.navigation.book_info.id); + window.location.href = `/book-documents.html?book_id=${this.navigation.book_info.id}`; } });