Files
document-server/frontend/static/js/book-editor.js
Hyungi Ahn e983d01a83 Fix: PDF 매칭 드롭다운 값 바인딩 문제 해결
- Alpine.js에서 :value="null"이 문자열로 변환되는 문제 수정
- option value를 빈 문자열("")로 변경
- JavaScript에서 null 값을 빈 문자열로 변환하여 UI 바인딩 개선
- 저장 시 빈 문자열과 null 모두 처리하도록 수정
2025-09-03 18:35:50 +09:00

300 lines
11 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.html_path.includes('/documents/') // HTML은 documents 폴더에 저장됨
)
.sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0)); // 순서대로 정렬
console.log('📄 서적 문서들:', this.documents.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 = "";
}
});
// 사용 가능한 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 => ({
title: pdf.title,
book_id: pdf.book_id,
book_title: pdf.book_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
})));
}
} 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;
}
},
// 뒤로가기
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 페이지 로드됨');
});