// 메인 애플리케이션 컴포넌트 window.documentApp = () => ({ // 상태 관리 documents: [], filteredDocuments: [], loading: false, error: '', // 인증 상태 isAuthenticated: false, currentUser: null, showLoginModal: false, // 필터링 및 검색 searchQuery: '', selectedTag: '', availableTags: [], // UI 상태 viewMode: 'grid', // 'grid' 또는 'list' user: null, // currentUser의 별칭 tags: [], // availableTags의 별칭 // 모달 상태 showUploadModal: false, // 초기화 async init() { await this.checkAuthStatus(); if (this.isAuthenticated) { await this.loadDocuments(); } this.setupEventListeners(); }, // 인증 상태 확인 async checkAuthStatus() { try { const token = localStorage.getItem('access_token'); if (token) { window.api.setToken(token); const user = await window.api.getCurrentUser(); this.isAuthenticated = true; this.currentUser = user; this.syncUIState(); // UI 상태 동기화 } } catch (error) { console.log('Not authenticated or token expired'); this.isAuthenticated = false; this.currentUser = null; localStorage.removeItem('access_token'); this.syncUIState(); // UI 상태 동기화 } }, // 로그인 모달 열기 openLoginModal() { this.showLoginModal = true; }, // 로그아웃 async logout() { try { await window.api.logout(); } catch (error) { console.error('Logout error:', error); } finally { this.isAuthenticated = false; this.currentUser = null; localStorage.removeItem('access_token'); localStorage.removeItem('refresh_token'); this.documents = []; this.filteredDocuments = []; } }, // 이벤트 리스너 설정 setupEventListeners() { // 문서 변경 이벤트 리스너 window.addEventListener('documents-changed', () => { this.loadDocuments(); }); // 알림 이벤트 리스너 window.addEventListener('show-notification', (event) => { this.showNotification(event.detail.message, event.detail.type); }); // 인증 상태 변경 이벤트 리스너 window.addEventListener('auth-changed', (event) => { this.isAuthenticated = event.detail.isAuthenticated; this.currentUser = event.detail.user; this.showLoginModal = false; this.syncUIState(); // UI 상태 동기화 if (this.isAuthenticated) { this.loadDocuments(); } }); // 로그인 모달 닫기 이벤트 리스너 window.addEventListener('close-login-modal', () => { this.showLoginModal = false; }); }, // 문서 목록 로드 async loadDocuments() { this.loading = true; this.error = ''; try { this.documents = await window.api.getDocuments(); this.updateAvailableTags(); this.filterDocuments(); this.syncUIState(); // UI 상태 동기화 } catch (error) { console.error('Failed to load documents:', error); this.error = 'Failed to load documents: ' + error.message; this.documents = []; } finally { this.loading = false; } }, // 사용 가능한 태그 업데이트 updateAvailableTags() { const tagSet = new Set(); this.documents.forEach(doc => { if (doc.tags) { doc.tags.forEach(tag => tagSet.add(tag)); } }); this.availableTags = Array.from(tagSet).sort(); this.tags = this.availableTags; // 별칭 동기화 }, // UI 상태 동기화 syncUIState() { this.user = this.currentUser; this.tags = this.availableTags; }, // 문서 필터링 filterDocuments() { let filtered = this.documents; // 검색어 필터링 if (this.searchQuery) { const query = this.searchQuery.toLowerCase(); filtered = filtered.filter(doc => doc.title.toLowerCase().includes(query) || (doc.description && doc.description.toLowerCase().includes(query)) || (doc.tags && doc.tags.some(tag => tag.toLowerCase().includes(query))) ); } // 태그 필터링 if (this.selectedTag) { filtered = filtered.filter(doc => doc.tags && doc.tags.includes(this.selectedTag) ); } this.filteredDocuments = filtered; }, // 검색어 변경 시 onSearchChange() { this.filterDocuments(); }, // 문서 검색 (HTML에서 사용) searchDocuments() { this.filterDocuments(); }, // 태그 선택 시 onTagSelect(tag) { this.selectedTag = this.selectedTag === tag ? '' : tag; this.filterDocuments(); }, // 태그 필터 초기화 clearTagFilter() { this.selectedTag = ''; this.filterDocuments(); }, // 문서 삭제 async deleteDocument(documentId) { if (!confirm('정말로 이 문서를 삭제하시겠습니까?')) { return; } try { await window.api.deleteDocument(documentId); await this.loadDocuments(); this.showNotification('문서가 삭제되었습니다', 'success'); } catch (error) { console.error('Failed to delete document:', error); this.showNotification('문서 삭제에 실패했습니다: ' + error.message, 'error'); } }, // 문서 보기 viewDocument(documentId) { window.open(`/viewer.html?id=${documentId}`, '_blank'); }, // 문서 열기 (HTML에서 사용) openDocument(documentId) { this.viewDocument(documentId); }, // 문서 수정 (HTML에서 사용) editDocument(document) { // TODO: 문서 수정 모달 구현 console.log('문서 수정:', document); alert('문서 수정 기능은 곧 구현됩니다!'); }, // 업로드 모달 열기 openUploadModal() { this.showUploadModal = true; }, // 업로드 모달 닫기 closeUploadModal() { this.showUploadModal = false; }, // 날짜 포맷팅 formatDate(dateString) { return new Date(dateString).toLocaleDateString('ko-KR', { year: 'numeric', month: 'long', day: 'numeric' }); }, // 알림 표시 showNotification(message, type = 'info') { // 간단한 알림 구현 (나중에 토스트 라이브러리로 교체 가능) const notification = document.createElement('div'); notification.className = `fixed top-4 right-4 p-4 rounded-lg text-white z-50 ${ type === 'error' ? 'bg-red-500' : type === 'success' ? 'bg-green-500' : 'bg-blue-500' }`; notification.textContent = message; document.body.appendChild(notification); setTimeout(() => { notification.remove(); }, 3000); } }); // 파일 업로드 컴포넌트 window.uploadModal = () => ({ uploading: false, uploadForm: { title: '', description: '', tags: '', is_public: false, document_date: '', html_file: null, pdf_file: null }, uploadError: '', // 서적 관련 상태 bookSelectionMode: 'none', // 'existing', 'new', 'none' bookSearchQuery: '', searchedBooks: [], selectedBook: null, newBook: { title: '', author: '', description: '' }, suggestions: [], searchTimeout: null, // 파일 선택 onFileSelect(event, fileType) { const file = event.target.files[0]; if (file) { this.uploadForm[fileType] = file; // HTML 파일의 경우 제목 자동 설정 if (fileType === 'html_file' && !this.uploadForm.title) { this.uploadForm.title = file.name.replace(/\.[^/.]+$/, ""); } } }, // 드래그 앤 드롭 처리 handleFileDrop(event, fileType) { event.target.classList.remove('dragover'); const files = event.dataTransfer.files; if (files.length > 0) { const file = files[0]; // 파일 타입 검증 if (fileType === 'html_file' && !file.name.match(/\.(html|htm)$/i)) { this.uploadError = 'HTML 파일만 업로드 가능합니다'; return; } if (fileType === 'pdf_file' && !file.name.match(/\.pdf$/i)) { this.uploadError = 'PDF 파일만 업로드 가능합니다'; return; } this.uploadForm[fileType] = file; this.uploadError = ''; // HTML 파일의 경우 제목 자동 설정 if (fileType === 'html_file' && !this.uploadForm.title) { this.uploadForm.title = file.name.replace(/\.[^/.]+$/, ""); } } }, // 업로드 실행 async upload() { // 필수 필드 검증 if (!this.uploadForm.html_file) { this.uploadError = 'HTML 파일을 선택해주세요'; return; } if (!this.uploadForm.title.trim()) { this.uploadError = '문서 제목을 입력해주세요'; return; } this.uploading = true; this.uploadError = ''; try { let bookId = null; // 서적 처리 if (this.bookSelectionMode === 'new' && this.newBook.title.trim()) { const newBook = await window.api.createBook({ title: this.newBook.title, author: this.newBook.author || null, description: this.newBook.description || null, language: this.uploadForm.language || 'ko', is_public: this.uploadForm.is_public }); bookId = newBook.id; } else if (this.bookSelectionMode === 'existing' && this.selectedBook) { bookId = this.selectedBook.id; } // FormData 생성 const formData = new FormData(); formData.append('title', this.uploadForm.title); formData.append('description', this.uploadForm.description || ''); formData.append('html_file', this.uploadForm.html_file); if (this.uploadForm.pdf_file) { formData.append('pdf_file', this.uploadForm.pdf_file); } // 서적 ID 추가 if (bookId) { formData.append('book_id', bookId); } formData.append('language', this.uploadForm.language || 'ko'); formData.append('is_public', this.uploadForm.is_public); if (this.uploadForm.tags) { formData.append('tags', this.uploadForm.tags); } if (this.uploadForm.document_date) { formData.append('document_date', this.uploadForm.document_date); } // 업로드 실행 await window.api.uploadDocument(formData); // 성공 처리 this.resetForm(); // 문서 목록 새로고침 window.dispatchEvent(new CustomEvent('documents-changed')); // 성공 알림 window.dispatchEvent(new CustomEvent('show-notification', { detail: { message: '문서가 성공적으로 업로드되었습니다', type: 'success' } })); } catch (error) { this.uploadError = error.message; } finally { this.uploading = false; } }, // 폼 리셋 resetForm() { this.uploadForm = { title: '', description: '', tags: '', is_public: false, document_date: '', html_file: null, pdf_file: null }; this.uploadError = ''; // 파일 입력 필드 리셋 const fileInputs = document.querySelectorAll('input[type="file"]'); fileInputs.forEach(input => input.value = ''); // 서적 관련 상태 리셋 this.bookSelectionMode = 'none'; this.bookSearchQuery = ''; this.searchedBooks = []; this.selectedBook = null; this.newBook = { title: '', author: '', description: '' }; this.suggestions = []; if (this.searchTimeout) { clearTimeout(this.searchTimeout); this.searchTimeout = null; } }, // 서적 검색 async searchBooks() { if (!this.bookSearchQuery.trim()) { this.searchedBooks = []; return; } try { const books = await window.api.searchBooks(this.bookSearchQuery, 10); this.searchedBooks = books; } catch (error) { console.error('서적 검색 실패:', error); this.searchedBooks = []; } }, // 서적 선택 selectBook(book) { this.selectedBook = book; this.bookSearchQuery = book.title; this.searchedBooks = []; }, // 유사도 추천 가져오기 async getSuggestions() { if (!this.newBook.title.trim()) { this.suggestions = []; return; } clearTimeout(this.searchTimeout); this.searchTimeout = setTimeout(async () => { try { const suggestions = await window.api.getBookSuggestions(this.newBook.title, 3); this.suggestions = suggestions.filter(s => s.similarity_score > 0.5); // 50% 이상 유사한 것만 } catch (error) { console.error('추천 가져오기 실패:', error); this.suggestions = []; } }, 300); }, // 추천에서 기존 서적 선택 selectExistingFromSuggestion(suggestion) { this.bookSelectionMode = 'existing'; this.selectedBook = suggestion; this.bookSearchQuery = suggestion.title; this.suggestions = []; this.newBook = { title: '', author: '', description: '' }; } });