Files
document-server/frontend/static/js/book-editor.js
Hyungi Ahn 04ae64fc4d feat: PDF/HTML 폴더 분리 및 필터링 개선
- 업로드 시 HTML과 PDF를 별도 폴더에 저장 (/documents/, /pdfs/)
- 프론트엔드 필터링을 폴더 경로 기준으로 단순화
- PDF 삭제 시 외래키 참조 해제 로직 추가
- book-documents.js, book-editor.js 필터링 통일
- HTML 문서 목록에서 PDF 완전 분리
2025-08-26 07:44:25 +09:00

247 lines
7.9 KiB
JavaScript

// 서적 편집 애플리케이션 컴포넌트
window.bookEditorApp = () => ({
// 상태 관리
documents: [],
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('bookId');
console.log('📖 편집할 서적 ID:', this.bookId);
},
// 인증 상태 확인
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 {
// 서적 정보 로드
this.bookInfo = await window.api.getBook(this.bookId);
console.log('📚 서적 정보 로드:', this.bookInfo);
// 모든 문서 가져와서 이 서적에 속한 HTML 문서들만 필터링 (폴더로 구분)
const allDocuments = await window.api.getDocuments();
this.documents = allDocuments
.filter(doc =>
doc.book_id === this.bookId &&
doc.html_path &&
!doc.pdf_path?.includes('/pdfs/') // pdfs 폴더에 있는 건 제외
)
.sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0)); // 순서대로 정렬
console.log('📄 서적 문서들:', this.documents.length, '개');
// 사용 가능한 PDF 문서들 로드 (PDF 타입 문서들)
// PDF 문서들만 필터링 (폴더 경로 기준)
this.availablePDFs = allDocuments.filter(doc =>
doc.pdf_path &&
doc.pdf_path.includes('/pdfs/') // PDF는 pdfs 폴더에 저장됨
);
console.log('📎 사용 가능한 PDF:', this.availablePDFs.length, '개');
} 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;
try {
// 서적 정보 업데이트
await window.api.updateBook(this.bookId, {
title: this.bookInfo.title,
author: this.bookInfo.author,
description: this.bookInfo.description
});
// 각 문서의 순서와 PDF 매칭 정보 업데이트
const updatePromises = this.documents.map(doc => {
return window.api.updateDocument(doc.id, {
sort_order: doc.sort_order,
matched_pdf_id: doc.matched_pdf_id || null
});
});
await Promise.all(updatePromises);
console.log('✅ 모든 변경사항 저장 완료');
this.showNotification('변경사항이 저장되었습니다', 'success');
// 잠시 후 서적 페이지로 돌아가기
setTimeout(() => {
this.goBack();
}, 1500);
} catch (error) {
console.error('저장 실패:', error);
this.showNotification('저장에 실패했습니다: ' + error.message, 'error');
} finally {
this.saving = false;
}
},
// 뒤로가기
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 페이지 로드됨');
});