// 서적 편집 애플리케이션 컴포넌트 window.bookEditorApp = () => ({ // 상태 관리 documents: [], // HTML 문서들 (순서 관리용) pdfDocuments: [], // PDF 전용 문서들 (별도 관리용) bookInfo: {}, availablePDFs: [], loading: false, saving: false, error: '', // 인증 상태 isAuthenticated: false, currentUser: null, // URL 파라미터 bookId: null, // SortableJS 인스턴스 sortableInstance: null, // 초기화 async init() { console.log('🚀 Book Editor App 초기화 시작'); // URL 파라미터 파싱 this.parseUrlParams(); // 인증 상태 확인 await this.checkAuthStatus(); if (this.isAuthenticated) { await this.loadBookData(); this.initSortable(); } // 헤더 로드 await this.loadHeader(); }, // URL 파라미터 파싱 parseUrlParams() { const urlParams = new URLSearchParams(window.location.search); this.bookId = urlParams.get('id') || urlParams.get('bookId'); // 'id' 또는 'bookId' 파라미터 지원 console.log('📖 편집할 서적 ID:', this.bookId); console.log('📖 URL 파라미터들:', Object.fromEntries(urlParams)); }, // 인증 상태 확인 async checkAuthStatus() { try { const user = await window.api.getCurrentUser(); this.isAuthenticated = true; this.currentUser = user; console.log('✅ 인증됨:', user.username); } catch (error) { console.log('❌ 인증되지 않음'); this.isAuthenticated = false; this.currentUser = null; window.location.href = '/login.html'; } }, // 헤더 로드 async loadHeader() { try { await window.headerLoader.loadHeader(); } catch (error) { console.error('헤더 로드 실패:', error); } }, // 서적 데이터 로드 async loadBookData() { this.loading = true; this.error = ''; try { console.log('🔍 서적 ID 확인:', this.bookId); // 서적 정보 로드 this.bookInfo = await window.api.getBook(this.bookId); console.log('📚 서적 정보 로드:', this.bookInfo); // 모든 문서 가져와서 이 서적에 속한 문서들 필터링 const allDocuments = await window.api.getAllDocuments(); // HTML 문서만 (순서 관리용) this.documents = allDocuments .filter(doc => doc.book_id === this.bookId && doc.html_path && doc.html_path.includes('/documents/') ) .sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0)); // 순서대로 정렬 // PDF 전용 문서들 (별도 관리용) this.pdfDocuments = allDocuments .filter(doc => doc.book_id === this.bookId && doc.pdf_path && !doc.html_path // PDF만 있는 문서 ) .sort((a, b) => new Date(b.created_at) - new Date(a.created_at)); // 최신순 console.log('📄 HTML 문서들:', this.documents.length, '개 (순서 관리)'); console.log('📕 PDF 전용 문서들:', this.pdfDocuments.length, '개 (별도 관리)'); // 각 문서의 PDF 매칭 상태 확인 this.documents.forEach((doc, index) => { console.log(`📄 문서 ${index + 1}: ${doc.title}`); console.log(` - matched_pdf_id: ${doc.matched_pdf_id || 'null'}`); console.log(` - sort_order: ${doc.sort_order || 'null'}`); // null 값을 빈 문자열로 변환 (UI 바인딩을 위해) if (doc.matched_pdf_id === null) { doc.matched_pdf_id = ""; } // 디버깅: 실제 값과 타입 확인 console.log(` - matched_pdf_id 타입: ${typeof doc.matched_pdf_id}`); console.log(` - matched_pdf_id 값: "${doc.matched_pdf_id}"`); console.log(` - 빈 문자열인가? ${doc.matched_pdf_id === ""}`); console.log(` - null인가? ${doc.matched_pdf_id === null}`); console.log(` - undefined인가? ${doc.matched_pdf_id === undefined}`); }); // 사용 가능한 PDF 문서들 로드 (현재 서적의 PDF만) console.log('🔍 현재 서적 ID:', this.bookId); console.log('🔍 전체 문서 수:', allDocuments.length); // PDF 문서들 먼저 필터링 const allPDFs = allDocuments.filter(doc => doc.pdf_path && doc.pdf_path.includes('/pdfs/') // PDF는 pdfs 폴더에 저장됨 ); console.log('🔍 전체 PDF 문서 수:', allPDFs.length); // 같은 서적의 PDF 문서들만 필터링 this.availablePDFs = allPDFs.filter(doc => { const match = String(doc.book_id) === String(this.bookId); if (!match && allPDFs.indexOf(doc) < 5) { console.log(`🔍 PDF "${doc.title}": book_id="${doc.book_id}" (${typeof doc.book_id}) vs bookId="${this.bookId}" (${typeof this.bookId})`); } return match; }); console.log('📎 현재 서적의 PDF:', this.availablePDFs.length, '개'); console.log('📎 현재 서적 PDF 목록:', this.availablePDFs.map(pdf => ({ id: pdf.id, title: pdf.title, book_id: pdf.book_id, book_title: pdf.book_title }))); // 각 PDF의 ID 확인 this.availablePDFs.forEach((pdf, index) => { console.log(`📎 PDF ${index + 1}: ID="${pdf.id}", 제목="${pdf.title}"`); }); // 디버깅: 다른 서적의 PDF들도 확인 const otherBookPDFs = allPDFs.filter(doc => doc.book_id !== this.bookId); console.log('🔍 다른 서적의 PDF:', otherBookPDFs.length, '개'); if (otherBookPDFs.length > 0) { console.log('🔍 다른 서적 PDF 예시:', otherBookPDFs.slice(0, 3).map(pdf => ({ title: pdf.title, book_id: pdf.book_id, book_title: pdf.book_title }))); } // Alpine.js DOM 업데이트 강제 실행 this.$nextTick(() => { console.log('🔄 Alpine.js DOM 업데이트 완료'); // DOM이 완전히 렌더링된 후 실행 setTimeout(() => { this.documents.forEach((doc, index) => { if (doc.matched_pdf_id) { console.log(`🔧 문서 ${index + 1} 강제 업데이트: ${doc.matched_pdf_id}`); // Alpine.js 반응성 트리거 const oldValue = doc.matched_pdf_id; doc.matched_pdf_id = ""; doc.matched_pdf_id = oldValue; } }); }, 100); }); } catch (error) { console.error('서적 데이터 로드 실패:', error); this.error = '데이터를 불러오는데 실패했습니다: ' + error.message; } finally { this.loading = false; } }, // SortableJS 초기화 initSortable() { this.$nextTick(() => { const sortableList = document.getElementById('sortable-list'); if (sortableList && !this.sortableInstance) { this.sortableInstance = Sortable.create(sortableList, { animation: 150, ghostClass: 'sortable-ghost', chosenClass: 'sortable-chosen', dragClass: 'sortable-drag', handle: '.fa-grip-vertical', onEnd: (evt) => { // 배열 순서 업데이트 const item = this.documents.splice(evt.oldIndex, 1)[0]; this.documents.splice(evt.newIndex, 0, item); this.updateDisplayOrder(); } }); console.log('✅ SortableJS 초기화 완료'); } }); }, // 표시 순서 업데이트 updateDisplayOrder() { this.documents.forEach((doc, index) => { doc.sort_order = index + 1; }); console.log('🔢 표시 순서 업데이트됨'); }, // 위로 이동 moveUp(index) { if (index > 0) { const item = this.documents.splice(index, 1)[0]; this.documents.splice(index - 1, 0, item); this.updateDisplayOrder(); } }, // 아래로 이동 moveDown(index) { if (index < this.documents.length - 1) { const item = this.documents.splice(index, 1)[0]; this.documents.splice(index + 1, 0, item); this.updateDisplayOrder(); } }, // 이름순 정렬 autoSortByName() { this.documents.sort((a, b) => { return a.title.localeCompare(b.title, 'ko', { numeric: true }); }); this.updateDisplayOrder(); console.log('📝 이름순 정렬 완료'); }, // 순서 뒤집기 reverseOrder() { this.documents.reverse(); this.updateDisplayOrder(); console.log('🔄 순서 뒤집기 완료'); }, // 변경사항 저장 async saveChanges() { if (this.saving) return; this.saving = true; console.log('💾 저장 시작...'); try { // 저장 전에 순서 업데이트 this.updateDisplayOrder(); // 서적 정보 업데이트 console.log('📚 서적 정보 업데이트 중...'); await window.api.updateBook(this.bookId, { title: this.bookInfo.title, author: this.bookInfo.author, description: this.bookInfo.description }); console.log('✅ 서적 정보 업데이트 완료'); // 각 문서의 순서와 PDF 매칭 정보 업데이트 console.log('📄 문서 업데이트 시작...'); const updatePromises = this.documents.map((doc, index) => { console.log(`📄 문서 ${index + 1}/${this.documents.length}: ${doc.title}`); console.log(` - sort_order: ${doc.sort_order}`); console.log(` - matched_pdf_id: ${doc.matched_pdf_id || 'null'}`); return window.api.updateDocument(doc.id, { sort_order: doc.sort_order, matched_pdf_id: doc.matched_pdf_id === "" || doc.matched_pdf_id === null ? null : doc.matched_pdf_id }); }); const results = await Promise.all(updatePromises); console.log('✅ 모든 문서 업데이트 완료:', results.length, '개'); console.log('✅ 모든 변경사항 저장 완료'); this.showNotification('변경사항이 저장되었습니다', 'success'); // 잠시 후 서적 페이지로 돌아가기 setTimeout(() => { this.goBack(); }, 1500); } catch (error) { console.error('❌ 저장 실패:', error); this.showNotification('저장에 실패했습니다: ' + error.message, 'error'); } finally { this.saving = false; } }, // 서적 정보 저장 async saveBookInfo() { try { await window.api.updateBook(this.bookId, { title: this.bookInfo.title, author: this.bookInfo.author, description: this.bookInfo.description }); this.showNotification('서적 정보가 저장되었습니다', 'success'); } catch (error) { console.error('서적 정보 저장 실패:', error); this.showNotification('서적 정보 저장에 실패했습니다: ' + error.message, 'error'); } }, // 서적 삭제 (모든 문서 포함) async deleteBook() { if (!this.bookInfo.title) { alert('서적 정보가 로드되지 않았습니다.'); return; } const confirmMessage = `"${this.bookInfo.title}" 서적을 완전히 삭제하시겠습니까?\n\n⚠️ 경고: 이 작업은 되돌릴 수 없습니다!\n\n삭제될 항목:\n- 서적 정보\n- HTML 문서 ${this.documents.length}개\n- PDF 전용 문서 ${this.pdfDocuments.length}개\n- 관련된 모든 하이라이트, 노트, 링크`; if (!confirm(confirmMessage)) { return; } // 한 번 더 확인 const finalConfirm = prompt(`정말로 삭제하시려면 서적 제목을 입력하세요:\n"${this.bookInfo.title}"`); if (finalConfirm !== this.bookInfo.title) { alert('서적 제목이 일치하지 않습니다. 삭제가 취소되었습니다.'); return; } try { // 서적에 속한 모든 HTML 문서 삭제 for (const doc of this.documents) { console.log(`🗑️ HTML 문서 삭제 중: ${doc.title}`); await window.api.deleteDocument(doc.id); } // 서적에 속한 모든 PDF 전용 문서 삭제 for (const doc of this.pdfDocuments) { console.log(`🗑️ PDF 문서 삭제 중: ${doc.title}`); await window.api.deleteDocument(doc.id); } // 서적 삭제 await window.api.deleteBook(this.bookId); alert('서적이 완전히 삭제되었습니다.'); // 메인 페이지로 이동 window.location.href = '/index.html'; } catch (error) { console.error('서적 삭제 실패:', error); alert('서적 삭제에 실패했습니다: ' + error.message); } }, // PDF 미리보기 previewPDF(pdf) { // PDF 뷰어 페이지로 이동 window.open(`/viewer.html?id=${pdf.id}`, '_blank'); }, // PDF 삭제 async deletePDF(pdf) { if (!confirm(`"${pdf.title}" PDF 문서를 삭제하시겠습니까?\n\n이 작업은 되돌릴 수 없습니다.`)) { return; } try { await window.api.deleteDocument(pdf.id); // PDF 목록에서 제거 this.pdfDocuments = this.pdfDocuments.filter(p => p.id !== pdf.id); this.showNotification('PDF 문서가 삭제되었습니다', 'success'); } catch (error) { console.error('PDF 삭제 실패:', error); this.showNotification('PDF 삭제에 실패했습니다: ' + error.message, 'error'); } }, // 뒤로가기 goBack() { window.location.href = `book-documents.html?bookId=${this.bookId}`; }, // 알림 표시 showNotification(message, type = 'info') { console.log(`${type.toUpperCase()}: ${message}`); // 간단한 토스트 알림 생성 const toast = document.createElement('div'); toast.className = `fixed top-4 right-4 px-6 py-3 rounded-lg text-white z-50 ${ type === 'success' ? 'bg-green-600' : type === 'error' ? 'bg-red-600' : 'bg-blue-600' }`; toast.textContent = message; document.body.appendChild(toast); // 3초 후 제거 setTimeout(() => { if (toast.parentNode) { toast.parentNode.removeChild(toast); } }, 3000); } }); // 페이지 로드 시 초기화 document.addEventListener('DOMContentLoaded', () => { console.log('📄 Book Editor 페이지 로드됨'); });