feat: 완전한 문서 관리 시스템 구현
✨ 주요 기능: - 문서 사라짐 문제 해결: API limit 제한으로 인한 문서 누락 해결 - 서적별 문서 관리: HTML과 PDF 통합 관리 시스템 - PDF 뷰어 개선: 인증, 네비게이션, 에러 처리 강화 - 서적 편집/삭제: 완전한 서적 관리 기능 🔧 기술적 개선: - /api/documents/all 엔드포인트 추가 (모든 문서 조회) - HTML/PDF 문서 타입별 아이콘 및 필터링 - 서적별 뷰에서 편집/삭제 버튼 추가 - PDF Manager와 서적 편집 페이지 연동 🎨 UI/UX 개선: - Devonthink 스타일 서적 그룹화 - HTML 문서 순서 관리와 PDF 관리 섹션 분리 - 문서 타입별 시각적 구분 (HTML: 파란색, PDF: 빨간색) - 2단계 확인을 통한 안전한 서적 삭제 �� 버그 수정: - PDF 삭제 시 undefined ID 전달 문제 해결 - 서적 편집 페이지 422 오류 해결 (URL 파라미터 문제) - PDF.js 워커 설정 및 인증 토큰 처리 개선
This commit is contained in:
@@ -83,12 +83,13 @@ router = APIRouter()
|
||||
@router.get("/", response_model=List[DocumentResponse])
|
||||
async def list_documents(
|
||||
skip: int = 0,
|
||||
limit: int = 50,
|
||||
limit: int = 50, # 기본값 복원
|
||||
tag: Optional[str] = None,
|
||||
search: Optional[str] = None,
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""페이지네이션이 있는 문서 목록 조회"""
|
||||
"""문서 목록 조회"""
|
||||
query = select(Document).options(
|
||||
selectinload(Document.uploader),
|
||||
@@ -160,6 +161,70 @@ async def list_documents(
|
||||
return response_data
|
||||
|
||||
|
||||
@router.get("/all", response_model=List[DocumentResponse])
|
||||
async def list_all_documents(
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
db: AsyncSession = Depends(get_db)
|
||||
):
|
||||
"""모든 문서 조회 (페이지네이션 없음) - 프론트엔드 전용"""
|
||||
|
||||
query = select(Document).options(
|
||||
selectinload(Document.uploader),
|
||||
selectinload(Document.tags),
|
||||
selectinload(Document.book), # 서적 정보 추가
|
||||
selectinload(Document.category) # 소분류 정보 추가
|
||||
)
|
||||
|
||||
# 권한 필터링 (관리자가 아니면 공개 문서 + 자신이 업로드한 문서만)
|
||||
if not current_user.is_admin:
|
||||
query = query.where(
|
||||
or_(
|
||||
Document.is_public == True,
|
||||
Document.uploaded_by == current_user.id
|
||||
)
|
||||
)
|
||||
|
||||
query = query.order_by(Document.created_at.desc())
|
||||
|
||||
result = await db.execute(query)
|
||||
documents = result.scalars().all()
|
||||
|
||||
# 응답 데이터 변환
|
||||
response_data = []
|
||||
for doc in documents:
|
||||
doc_data = DocumentResponse(
|
||||
id=str(doc.id),
|
||||
title=doc.title,
|
||||
description=doc.description,
|
||||
html_path=doc.html_path, # None 가능 (PDF만 업로드한 경우)
|
||||
pdf_path=doc.pdf_path,
|
||||
thumbnail_path=doc.thumbnail_path,
|
||||
file_size=doc.file_size,
|
||||
page_count=doc.page_count,
|
||||
language=doc.language,
|
||||
is_public=doc.is_public,
|
||||
is_processed=doc.is_processed,
|
||||
created_at=doc.created_at,
|
||||
updated_at=doc.updated_at,
|
||||
document_date=doc.document_date,
|
||||
uploader_name=doc.uploader.full_name or doc.uploader.email,
|
||||
tags=[tag.name for tag in doc.tags],
|
||||
# 서적 정보 추가
|
||||
book_id=str(doc.book.id) if doc.book else None,
|
||||
book_title=doc.book.title if doc.book else None,
|
||||
book_author=doc.book.author if doc.book else None,
|
||||
# 소분류 정보 추가
|
||||
category_id=str(doc.category.id) if doc.category else None,
|
||||
category_name=doc.category.name if doc.category else None,
|
||||
sort_order=doc.sort_order,
|
||||
# PDF 매칭 정보 추가
|
||||
matched_pdf_id=str(doc.matched_pdf_id) if doc.matched_pdf_id else None
|
||||
)
|
||||
response_data.append(doc_data)
|
||||
|
||||
return response_data
|
||||
|
||||
|
||||
@router.get("/hierarchy/structured", response_model=dict)
|
||||
async def get_documents_by_hierarchy(
|
||||
current_user: User = Depends(get_current_active_user),
|
||||
@@ -375,10 +440,14 @@ async def upload_document(
|
||||
|
||||
await db.commit()
|
||||
|
||||
# 문서 정보를 다시 로드 (태그 포함)
|
||||
# 문서 정보를 다시 로드 (태그, 서적, 카테고리 포함)
|
||||
result = await db.execute(
|
||||
select(Document)
|
||||
.options(selectinload(Document.tags))
|
||||
.options(
|
||||
selectinload(Document.tags),
|
||||
selectinload(Document.book),
|
||||
selectinload(Document.category)
|
||||
)
|
||||
.where(Document.id == document.id)
|
||||
)
|
||||
document_with_tags = result.scalar_one()
|
||||
@@ -401,6 +470,14 @@ async def upload_document(
|
||||
document_date=document_with_tags.document_date,
|
||||
uploader_name=current_user.full_name or current_user.email,
|
||||
tags=[tag.name for tag in document_with_tags.tags],
|
||||
# 서적 정보 추가
|
||||
book_id=str(document_with_tags.book.id) if document_with_tags.book else None,
|
||||
book_title=document_with_tags.book.title if document_with_tags.book else None,
|
||||
book_author=document_with_tags.book.author if document_with_tags.book else None,
|
||||
# 소분류 정보 추가
|
||||
category_id=str(document_with_tags.category.id) if document_with_tags.category else None,
|
||||
category_name=document_with_tags.category.name if document_with_tags.category else None,
|
||||
sort_order=document_with_tags.sort_order,
|
||||
matched_pdf_id=str(document_with_tags.matched_pdf_id) if document_with_tags.matched_pdf_id else None
|
||||
)
|
||||
|
||||
|
||||
@@ -95,16 +95,29 @@
|
||||
rows="3"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"></textarea>
|
||||
</div>
|
||||
|
||||
<!-- 서적 관리 버튼들 -->
|
||||
<div class="flex justify-between pt-4 border-t border-gray-200">
|
||||
<button @click="deleteBook()"
|
||||
class="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 transition-colors">
|
||||
<i class="fas fa-trash mr-2"></i>서적 삭제
|
||||
</button>
|
||||
|
||||
<button @click="saveBookInfo()"
|
||||
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
|
||||
<i class="fas fa-save mr-2"></i>서적 정보 저장
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 문서 순서 및 PDF 매칭 편집 -->
|
||||
<!-- HTML 문서 순서 및 PDF 매칭 편집 -->
|
||||
<div class="bg-white rounded-lg shadow-sm border">
|
||||
<div class="p-6 border-b border-gray-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<h2 class="text-lg font-semibold text-gray-900 flex items-center">
|
||||
<i class="fas fa-list-ol mr-2 text-green-600"></i>
|
||||
문서 순서 및 PDF 매칭
|
||||
<i class="fas fa-list-ol mr-2 text-blue-600"></i>
|
||||
HTML 문서 순서 및 PDF 매칭
|
||||
</h2>
|
||||
<div class="flex space-x-2">
|
||||
<button @click="autoSortByName()"
|
||||
@@ -138,15 +151,22 @@
|
||||
|
||||
<!-- 문서 정보 -->
|
||||
<div class="flex-1">
|
||||
<h3 class="font-medium text-gray-900" x-text="doc.title"></h3>
|
||||
<div class="flex items-center space-x-2">
|
||||
<!-- 문서 타입 아이콘 -->
|
||||
<i x-show="doc.html_path && !doc.pdf_path" class="fas fa-file-alt text-blue-500" title="HTML 문서"></i>
|
||||
<i x-show="doc.pdf_path && !doc.html_path" class="fas fa-file-pdf text-red-500" title="PDF 문서"></i>
|
||||
<i x-show="doc.html_path && doc.pdf_path" class="fas fa-file-archive text-purple-500" title="HTML + PDF"></i>
|
||||
|
||||
<h3 class="font-medium text-gray-900" x-text="doc.title"></h3>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500" x-text="doc.description || '설명 없음'"></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PDF 매칭 및 컨트롤 -->
|
||||
<div class="flex items-center space-x-3">
|
||||
<!-- PDF 매칭 드롭다운 -->
|
||||
<div class="min-w-48 relative">
|
||||
<!-- PDF 매칭 드롭다운 (HTML 문서만) -->
|
||||
<div x-show="doc.html_path" class="min-w-48 relative">
|
||||
<select x-model="doc.matched_pdf_id"
|
||||
:class="doc.matched_pdf_id ? 'border-green-300 bg-green-50' : 'border-gray-300'"
|
||||
class="w-full px-3 py-2 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent text-sm">
|
||||
@@ -187,6 +207,67 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PDF 전용 문서 관리 -->
|
||||
<div class="bg-white rounded-lg shadow-sm border">
|
||||
<div class="p-6 border-b border-gray-200">
|
||||
<h2 class="text-lg font-semibold text-gray-900 flex items-center">
|
||||
<i class="fas fa-file-pdf mr-2 text-red-600"></i>
|
||||
등록된 PDF 문서 관리
|
||||
<span class="ml-2 px-2 py-1 bg-red-100 text-red-700 text-xs rounded-full" x-text="pdfDocuments.length + '개'"></span>
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<div class="p-6">
|
||||
<!-- PDF 문서 목록 -->
|
||||
<div class="space-y-3">
|
||||
<template x-for="(pdf, index) in pdfDocuments" :key="pdf.id">
|
||||
<div class="bg-red-50 border border-red-200 rounded-lg p-4 hover:bg-red-100 transition-colors">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center flex-1">
|
||||
<!-- PDF 아이콘 -->
|
||||
<div class="w-10 h-10 bg-red-600 text-white rounded-full flex items-center justify-center text-sm font-medium mr-4">
|
||||
<i class="fas fa-file-pdf"></i>
|
||||
</div>
|
||||
|
||||
<!-- PDF 정보 -->
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center space-x-2">
|
||||
<h3 class="font-medium text-gray-900" x-text="pdf.title"></h3>
|
||||
<span class="px-2 py-1 bg-red-100 text-red-700 text-xs rounded-full">PDF 전용</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-500" x-text="pdf.description || '설명 없음'"></p>
|
||||
<div class="flex items-center space-x-4 text-xs text-gray-400 mt-1">
|
||||
<span x-text="pdf.original_filename"></span>
|
||||
<span x-text="new Date(pdf.created_at).toLocaleDateString()"></span>
|
||||
<span x-show="pdf.file_size" x-text="Math.round(pdf.file_size / 1024) + 'KB'"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- PDF 관리 버튼들 -->
|
||||
<div class="flex items-center space-x-2">
|
||||
<button @click="previewPDF(pdf)"
|
||||
class="px-3 py-1.5 bg-blue-100 text-blue-700 rounded-md hover:bg-blue-200 transition-colors text-sm">
|
||||
<i class="fas fa-eye mr-1"></i>미리보기
|
||||
</button>
|
||||
<button @click="deletePDF(pdf)"
|
||||
class="px-3 py-1.5 bg-red-100 text-red-700 rounded-md hover:bg-red-200 transition-colors text-sm">
|
||||
<i class="fas fa-trash mr-1"></i>삭제
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<!-- 빈 상태 -->
|
||||
<div x-show="pdfDocuments.length === 0" class="text-center py-8">
|
||||
<i class="fas fa-file-pdf text-gray-400 text-3xl mb-4"></i>
|
||||
<p class="text-gray-500">등록된 PDF 문서가 없습니다</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
@@ -150,7 +150,10 @@
|
||||
<div class="p-6">
|
||||
<div class="flex items-start justify-between mb-4">
|
||||
<h3 class="text-lg font-semibold text-gray-900 line-clamp-2" x-text="doc.title"></h3>
|
||||
<i class="fas fa-file-alt text-blue-500 text-xl"></i>
|
||||
<!-- 문서 타입별 아이콘 -->
|
||||
<i x-show="doc.html_path && !doc.pdf_path" class="fas fa-file-alt text-blue-500 text-xl" title="HTML 문서"></i>
|
||||
<i x-show="doc.pdf_path && !doc.html_path" class="fas fa-file-pdf text-red-500 text-xl" title="PDF 문서"></i>
|
||||
<i x-show="doc.html_path && doc.pdf_path" class="fas fa-file-archive text-purple-500 text-xl" title="HTML + PDF"></i>
|
||||
</div>
|
||||
|
||||
<p class="text-gray-600 text-sm mb-3 line-clamp-3" x-text="doc.description || '설명 없음'"></p>
|
||||
@@ -222,11 +225,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 확장/축소 아이콘 -->
|
||||
<!-- 서적 관리 버튼들 -->
|
||||
<div class="flex items-center space-x-2">
|
||||
<button @click.stop="openBookDocuments(bookGroup.book)"
|
||||
class="px-3 py-1 text-xs bg-blue-100 text-blue-700 rounded-full hover:bg-blue-200 transition-colors">
|
||||
편집
|
||||
<!-- 서적 편집 버튼 -->
|
||||
<button x-show="bookGroup.book?.id"
|
||||
@click.stop="window.location.href = `/book-editor.html?id=${bookGroup.book.id}`"
|
||||
class="px-3 py-1 text-xs bg-blue-100 text-blue-700 rounded-md hover:bg-blue-200 transition-colors"
|
||||
title="서적 편집">
|
||||
<i class="fas fa-edit mr-1"></i>편집
|
||||
</button>
|
||||
|
||||
<!-- 서적 삭제 버튼 -->
|
||||
<button x-show="bookGroup.book?.id"
|
||||
@click.stop="deleteBook(bookGroup.book)"
|
||||
class="px-3 py-1 text-xs bg-red-100 text-red-700 rounded-md hover:bg-red-200 transition-colors"
|
||||
title="서적 삭제">
|
||||
<i class="fas fa-trash mr-1"></i>삭제
|
||||
</button>
|
||||
<i class="fas fa-chevron-down text-gray-400 transition-transform duration-200"
|
||||
:class="{'rotate-180': bookGroup.expanded}"></i>
|
||||
@@ -243,8 +257,17 @@
|
||||
<div class="flex items-center space-x-3 flex-1">
|
||||
<!-- 문서 타입 아이콘 -->
|
||||
<div class="w-8 h-8 rounded-md flex items-center justify-center"
|
||||
:class="doc.pdf_path ? 'bg-red-100 text-red-600' : 'bg-gray-100 text-gray-600'">
|
||||
<i :class="doc.pdf_path ? 'fas fa-file-pdf' : 'fas fa-file-alt'" class="text-xs"></i>
|
||||
:class="{
|
||||
'bg-blue-100 text-blue-600': doc.html_path && !doc.pdf_path,
|
||||
'bg-red-100 text-red-600': doc.pdf_path && !doc.html_path,
|
||||
'bg-purple-100 text-purple-600': doc.html_path && doc.pdf_path
|
||||
}">
|
||||
<i class="text-xs"
|
||||
:class="{
|
||||
'fas fa-file-alt': doc.html_path && !doc.pdf_path,
|
||||
'fas fa-file-pdf': doc.pdf_path && !doc.html_path,
|
||||
'fas fa-file-archive': doc.html_path && doc.pdf_path
|
||||
}"></i>
|
||||
</div>
|
||||
|
||||
<!-- 문서 정보 -->
|
||||
|
||||
@@ -279,8 +279,16 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 확장/축소 아이콘 -->
|
||||
<!-- 서적 관리 버튼들 -->
|
||||
<div class="flex items-center space-x-2">
|
||||
<!-- 서적 편집 버튼 -->
|
||||
<button x-show="bookGroup.book?.id"
|
||||
@click.stop="window.location.href = `/book-editor.html?id=${bookGroup.book.id}`"
|
||||
class="px-3 py-1 text-xs bg-blue-100 text-blue-700 rounded-md hover:bg-blue-200 transition-colors"
|
||||
title="서적 편집">
|
||||
<i class="fas fa-edit mr-1"></i>편집
|
||||
</button>
|
||||
|
||||
<span class="text-xs text-gray-500" x-text="bookGroup.pdfs.length + '개'"></span>
|
||||
<i class="fas fa-chevron-down text-gray-400 transition-transform duration-200"
|
||||
:class="{'rotate-180': bookGroup.expanded}"></i>
|
||||
@@ -319,7 +327,7 @@
|
||||
title="다운로드">
|
||||
<i class="fas fa-download text-xs"></i>
|
||||
</button>
|
||||
<button @click.stop="deletePDF(pdf.id)"
|
||||
<button @click.stop="deletePDF(pdf)"
|
||||
class="p-2 text-gray-400 hover:text-red-600 transition-colors rounded-md hover:bg-red-50"
|
||||
title="삭제">
|
||||
<i class="fas fa-trash text-xs"></i>
|
||||
|
||||
@@ -213,6 +213,11 @@ class DocumentServerAPI {
|
||||
return await this.get('/documents/', params);
|
||||
}
|
||||
|
||||
async getAllDocuments() {
|
||||
// 모든 문서를 가져오는 전용 엔드포인트
|
||||
return await this.get('/documents/all');
|
||||
}
|
||||
|
||||
async getDocumentsHierarchy() {
|
||||
return await this.get('/documents/hierarchy/structured');
|
||||
}
|
||||
@@ -429,6 +434,14 @@ class DocumentServerAPI {
|
||||
return await this.post('/books', bookData);
|
||||
}
|
||||
|
||||
async updateBook(bookId, bookData) {
|
||||
return await this.put(`/books/${bookId}`, bookData);
|
||||
}
|
||||
|
||||
async deleteBook(bookId) {
|
||||
return await this.delete(`/books/${bookId}`);
|
||||
}
|
||||
|
||||
async getBook(bookId) {
|
||||
return await this.get(`/books/${bookId}`);
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ window.bookDocumentsApp = () => ({
|
||||
|
||||
try {
|
||||
// 모든 문서 가져오기
|
||||
const allDocuments = await window.api.getDocuments();
|
||||
const allDocuments = await window.api.getAllDocuments();
|
||||
|
||||
if (this.bookId === 'none') {
|
||||
// 서적 미분류 HTML 문서들만 (폴더로 구분)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
// 서적 편집 애플리케이션 컴포넌트
|
||||
window.bookEditorApp = () => ({
|
||||
// 상태 관리
|
||||
documents: [],
|
||||
documents: [], // HTML 문서들 (순서 관리용)
|
||||
pdfDocuments: [], // PDF 전용 문서들 (별도 관리용)
|
||||
bookInfo: {},
|
||||
availablePDFs: [],
|
||||
loading: false,
|
||||
@@ -40,8 +41,9 @@ window.bookEditorApp = () => ({
|
||||
// URL 파라미터 파싱
|
||||
parseUrlParams() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
this.bookId = urlParams.get('bookId');
|
||||
this.bookId = urlParams.get('id') || urlParams.get('bookId'); // 'id' 또는 'bookId' 파라미터 지원
|
||||
console.log('📖 편집할 서적 ID:', this.bookId);
|
||||
console.log('📖 URL 파라미터들:', Object.fromEntries(urlParams));
|
||||
},
|
||||
|
||||
// 인증 상태 확인
|
||||
@@ -74,21 +76,35 @@ window.bookEditorApp = () => ({
|
||||
this.error = '';
|
||||
|
||||
try {
|
||||
console.log('🔍 서적 ID 확인:', this.bookId);
|
||||
|
||||
// 서적 정보 로드
|
||||
this.bookInfo = await window.api.getBook(this.bookId);
|
||||
console.log('📚 서적 정보 로드:', this.bookInfo);
|
||||
|
||||
// 모든 문서 가져와서 이 서적에 속한 HTML 문서들만 필터링 (폴더로 구분)
|
||||
const allDocuments = await window.api.getDocuments();
|
||||
// 모든 문서 가져와서 이 서적에 속한 문서들 필터링
|
||||
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/') // HTML은 documents 폴더에 저장됨
|
||||
doc.html_path &&
|
||||
doc.html_path.includes('/documents/')
|
||||
)
|
||||
.sort((a, b) => (a.sort_order || 0) - (b.sort_order || 0)); // 순서대로 정렬
|
||||
|
||||
console.log('📄 서적 문서들:', this.documents.length, '개');
|
||||
// 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) => {
|
||||
@@ -295,6 +311,93 @@ window.bookEditorApp = () => ({
|
||||
}
|
||||
},
|
||||
|
||||
// 서적 정보 저장
|
||||
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}`;
|
||||
|
||||
@@ -134,7 +134,10 @@ window.documentApp = () => ({
|
||||
this.error = '';
|
||||
|
||||
try {
|
||||
const allDocuments = await window.api.getDocuments();
|
||||
const allDocuments = await window.api.getAllDocuments();
|
||||
|
||||
// 디버깅: API 응답 원본 확인
|
||||
console.log('🔍 API 응답 원본 (첫 3개):', JSON.stringify(allDocuments.slice(0, 3), null, 2));
|
||||
|
||||
// HTML 문서만 필터링 (PDF 파일 제외)
|
||||
this.documents = allDocuments.filter(doc =>
|
||||
@@ -146,6 +149,19 @@ window.documentApp = () => ({
|
||||
console.log('📄 HTML 문서:', this.documents.length, '개');
|
||||
console.log('📄 PDF 파일:', allDocuments.length - this.documents.length, '개 (제외됨)');
|
||||
|
||||
// 디버깅: 사라진 문서 찾기
|
||||
console.log('🔍 HTML 경로가 있는 문서들:');
|
||||
allDocuments.forEach(doc => {
|
||||
if (doc.html_path) {
|
||||
console.log(` - ${doc.title}: ${doc.html_path}`);
|
||||
}
|
||||
});
|
||||
|
||||
console.log('🔍 필터링된 문서들:');
|
||||
this.documents.forEach(doc => {
|
||||
console.log(` - ${doc.title}: ${doc.html_path}`);
|
||||
});
|
||||
|
||||
this.updateAvailableTags();
|
||||
this.filterDocuments();
|
||||
this.syncUIState(); // UI 상태 동기화
|
||||
@@ -206,6 +222,32 @@ window.documentApp = () => ({
|
||||
this.filterDocuments();
|
||||
},
|
||||
|
||||
// 서적 삭제
|
||||
async deleteBook(book) {
|
||||
if (!book || !book.id) {
|
||||
alert('서적 정보가 올바르지 않습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
const confirmMessage = `"${book.title}" 서적을 삭제하시겠습니까?\n\n⚠️ 주의: 이 서적에 속한 모든 문서들이 '서적 미분류'로 이동됩니다.`;
|
||||
|
||||
if (!confirm(confirmMessage)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await window.api.deleteBook(book.id);
|
||||
|
||||
// 문서 목록 다시 로드
|
||||
await this.loadDocuments();
|
||||
|
||||
alert('서적이 삭제되었습니다.');
|
||||
} catch (error) {
|
||||
console.error('서적 삭제 실패:', error);
|
||||
alert('서적 삭제에 실패했습니다: ' + error.message);
|
||||
}
|
||||
},
|
||||
|
||||
// 필터 초기화
|
||||
clearFilters() {
|
||||
this.searchQuery = '';
|
||||
|
||||
@@ -67,7 +67,7 @@ window.pdfManagerApp = () => ({
|
||||
|
||||
try {
|
||||
// 모든 문서 가져오기
|
||||
this.allDocuments = await window.api.getDocuments();
|
||||
this.allDocuments = await window.api.getAllDocuments();
|
||||
|
||||
// PDF 파일들만 필터링
|
||||
this.pdfDocuments = this.allDocuments.filter(doc =>
|
||||
|
||||
BIN
uploads/pdfs/0271c01d-096e-47ef-909e-b31b42c24f72.pdf
Normal file
BIN
uploads/pdfs/0271c01d-096e-47ef-909e-b31b42c24f72.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/044257e1-8f60-4ee4-9ccf-d765d0239fd5.pdf
Normal file
BIN
uploads/pdfs/044257e1-8f60-4ee4-9ccf-d765d0239fd5.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/0a789d13-9c7d-4f64-bc33-311c2bf4e9c4.pdf
Normal file
BIN
uploads/pdfs/0a789d13-9c7d-4f64-bc33-311c2bf4e9c4.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/0ba7d965-7132-438a-9796-5b39322f4ca6.pdf
Normal file
BIN
uploads/pdfs/0ba7d965-7132-438a-9796-5b39322f4ca6.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/1784a4ff-0fcf-4bc6-a1ef-dbf2948dd2ad.pdf
Normal file
BIN
uploads/pdfs/1784a4ff-0fcf-4bc6-a1ef-dbf2948dd2ad.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/1a444bf5-eb9a-4e8e-b0d8-1f767cbab5e1.pdf
Normal file
BIN
uploads/pdfs/1a444bf5-eb9a-4e8e-b0d8-1f767cbab5e1.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/1cf885e9-c425-4a8d-90e4-fd2fb704ffad.pdf
Normal file
BIN
uploads/pdfs/1cf885e9-c425-4a8d-90e4-fd2fb704ffad.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/237da603-1d16-43c4-b09b-e1e19a2a3e23.pdf
Normal file
BIN
uploads/pdfs/237da603-1d16-43c4-b09b-e1e19a2a3e23.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/425cf9a8-69f3-4b64-944a-ca7dc150bea9.pdf
Normal file
BIN
uploads/pdfs/425cf9a8-69f3-4b64-944a-ca7dc150bea9.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/45128e60-b6cb-46e0-a33c-fb8523deb7ad.pdf
Normal file
BIN
uploads/pdfs/45128e60-b6cb-46e0-a33c-fb8523deb7ad.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/4e767e23-d3d9-4df9-a683-2ceb9719e709.pdf
Normal file
BIN
uploads/pdfs/4e767e23-d3d9-4df9-a683-2ceb9719e709.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/5667922f-5878-4258-a3db-fb471e6531b1.pdf
Normal file
BIN
uploads/pdfs/5667922f-5878-4258-a3db-fb471e6531b1.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/59f6939c-d85d-42e7-987c-47ceeff9c78e.pdf
Normal file
BIN
uploads/pdfs/59f6939c-d85d-42e7-987c-47ceeff9c78e.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/5aafb935-e989-4f86-9c3a-5dd3f5ac7b1e.pdf
Normal file
BIN
uploads/pdfs/5aafb935-e989-4f86-9c3a-5dd3f5ac7b1e.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/5d4f3282-229b-4566-b6dd-e264623953dc_additional.pdf
Normal file
BIN
uploads/pdfs/5d4f3282-229b-4566-b6dd-e264623953dc_additional.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/60555ee8-6517-4e34-8c9d-1924d9bb4355.pdf
Normal file
BIN
uploads/pdfs/60555ee8-6517-4e34-8c9d-1924d9bb4355.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/65f56284-8cca-4b8a-8976-b1ac6600f33b.pdf
Normal file
BIN
uploads/pdfs/65f56284-8cca-4b8a-8976-b1ac6600f33b.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/6d6e63f2-e05d-45fa-a8d5-4a155b18405c.pdf
Normal file
BIN
uploads/pdfs/6d6e63f2-e05d-45fa-a8d5-4a155b18405c.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/79e33e90-1ced-402e-b1df-3251a68e3e71.pdf
Normal file
BIN
uploads/pdfs/79e33e90-1ced-402e-b1df-3251a68e3e71.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/7ed83ee2-9f35-41b8-8508-b72229084198.pdf
Normal file
BIN
uploads/pdfs/7ed83ee2-9f35-41b8-8508-b72229084198.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/81d2c796-e450-4f23-a8c8-5c14ad019e88.pdf
Normal file
BIN
uploads/pdfs/81d2c796-e450-4f23-a8c8-5c14ad019e88.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/86bd40ab-369d-4dc4-bd52-a6787f806642.pdf
Normal file
BIN
uploads/pdfs/86bd40ab-369d-4dc4-bd52-a6787f806642.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/871ed168-eecf-442b-9771-f86662accf69.pdf
Normal file
BIN
uploads/pdfs/871ed168-eecf-442b-9771-f86662accf69.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/88494df8-ac20-44d3-85e6-361d55ccae0e.pdf
Normal file
BIN
uploads/pdfs/88494df8-ac20-44d3-85e6-361d55ccae0e.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/8c55189e-a570-4b74-89c5-47954e5cdd49.pdf
Normal file
BIN
uploads/pdfs/8c55189e-a570-4b74-89c5-47954e5cdd49.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/923ed2b0-3aa1-414e-a083-5c5cef2d9ad3.pdf
Normal file
BIN
uploads/pdfs/923ed2b0-3aa1-414e-a083-5c5cef2d9ad3.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/92bfb634-60aa-4186-b5bf-ef78d10bb19c.pdf
Normal file
BIN
uploads/pdfs/92bfb634-60aa-4186-b5bf-ef78d10bb19c.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/96efd9ec-261d-4542-b406-1f78e9e40223.pdf
Normal file
BIN
uploads/pdfs/96efd9ec-261d-4542-b406-1f78e9e40223.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/9c712a1b-8e44-47ea-bf0d-429d7aa855f9.pdf
Normal file
BIN
uploads/pdfs/9c712a1b-8e44-47ea-bf0d-429d7aa855f9.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/a284c509-5931-48f0-acc5-18c1788b826a.pdf
Normal file
BIN
uploads/pdfs/a284c509-5931-48f0-acc5-18c1788b826a.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/a79308d0-3947-43a3-983d-ac4fe39b3dae.pdf
Normal file
BIN
uploads/pdfs/a79308d0-3947-43a3-983d-ac4fe39b3dae.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/adaeeec4-241f-44b3-82fe-b6653f541d48.pdf
Normal file
BIN
uploads/pdfs/adaeeec4-241f-44b3-82fe-b6653f541d48.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/ae849cd7-0b48-403f-8ae4-6f544c45a5a8.pdf
Normal file
BIN
uploads/pdfs/ae849cd7-0b48-403f-8ae4-6f544c45a5a8.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/b081d72c-3a4a-45ec-a0c9-1fbfc60f0dee.pdf
Normal file
BIN
uploads/pdfs/b081d72c-3a4a-45ec-a0c9-1fbfc60f0dee.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/b4f5f979-f5ca-49cd-8fa7-05bd2680dc44.pdf
Normal file
BIN
uploads/pdfs/b4f5f979-f5ca-49cd-8fa7-05bd2680dc44.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/ba71be69-faaf-4197-a9d3-57bc08c1d80a.pdf
Normal file
BIN
uploads/pdfs/ba71be69-faaf-4197-a9d3-57bc08c1d80a.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/c8eeb6ec-a7a7-4534-83e5-758453f3aca4.pdf
Normal file
BIN
uploads/pdfs/c8eeb6ec-a7a7-4534-83e5-758453f3aca4.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/c93c4310-5371-4f92-883a-2edc779ac9c3.pdf
Normal file
BIN
uploads/pdfs/c93c4310-5371-4f92-883a-2edc779ac9c3.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/cd790600-28aa-472d-9e54-db3cbed0097c.pdf
Normal file
BIN
uploads/pdfs/cd790600-28aa-472d-9e54-db3cbed0097c.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/d55d7866-ea97-4271-9142-8beb53ba4229.pdf
Normal file
BIN
uploads/pdfs/d55d7866-ea97-4271-9142-8beb53ba4229.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/e5bb3cb9-3d5f-4e9c-9441-60a110ea2a3c.pdf
Normal file
BIN
uploads/pdfs/e5bb3cb9-3d5f-4e9c-9441-60a110ea2a3c.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/e98ec815-78c6-4aac-9e9e-9a7b65d31cdc.pdf
Normal file
BIN
uploads/pdfs/e98ec815-78c6-4aac-9e9e-9a7b65d31cdc.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/f22d9bad-fe0a-4773-a073-534d60913f67.pdf
Normal file
BIN
uploads/pdfs/f22d9bad-fe0a-4773-a073-534d60913f67.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/f84f9929-6151-461b-93fc-995707badeb3.pdf
Normal file
BIN
uploads/pdfs/f84f9929-6151-461b-93fc-995707badeb3.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/fcb7dafd-a7d4-4b47-bc7c-8db6080ffd1f.pdf
Normal file
BIN
uploads/pdfs/fcb7dafd-a7d4-4b47-bc7c-8db6080ffd1f.pdf
Normal file
Binary file not shown.
BIN
uploads/pdfs/fe8e9764-0f45-4c52-9be2-11f0cc478b4d.pdf
Normal file
BIN
uploads/pdfs/fe8e9764-0f45-4c52-9be2-11f0cc478b4d.pdf
Normal file
Binary file not shown.
Reference in New Issue
Block a user