주요 변경사항: - story-reader.html: pt-4 → pt-20 - pdf-manager.html: py-8 → pt-20 pb-8 - upload.html: py-8 → pt-20 pb-8 - index.html: py-8 → pt-20 pb-8 - search.html: py-8 → pt-20 pb-8 - memo-tree.html: pt-16 → pt-20 - notebooks.html: py-8 → pt-20 pb-8 - notes.html: py-8 → pt-20 pb-8 헤더(h-16, z-50)와의 충돌을 방지하기 위해 모든 페이지의 상단 여백을 pt-20(80px)으로 통일
341 lines
19 KiB
HTML
341 lines
19 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>문서 업로드 - Document Server</title>
|
|
<script src="https://cdn.tailwindcss.com"></script>
|
|
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
|
<script src="https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js"></script>
|
|
|
|
<style>
|
|
.drag-area {
|
|
border: 2px dashed #cbd5e1;
|
|
transition: all 0.3s ease;
|
|
}
|
|
.drag-area.drag-over {
|
|
border-color: #3b82f6;
|
|
background-color: #eff6ff;
|
|
}
|
|
.file-item {
|
|
transition: all 0.3s ease;
|
|
}
|
|
.file-item:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
}
|
|
.sortable-ghost {
|
|
opacity: 0.4;
|
|
}
|
|
.sortable-chosen {
|
|
transform: rotate(5deg);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="bg-gray-50 min-h-screen" x-data="uploadApp()">
|
|
<!-- 헤더 -->
|
|
<div id="header-container"></div>
|
|
|
|
<!-- 메인 컨텐츠 -->
|
|
<main class="container mx-auto px-4 pt-20 pb-8">
|
|
<!-- 페이지 헤더 -->
|
|
<div class="mb-8">
|
|
<div class="flex items-center space-x-4 mb-4">
|
|
<button @click="goBack()"
|
|
class="flex items-center px-4 py-2 bg-gray-200 hover:bg-gray-300 rounded-lg transition-colors">
|
|
<i class="fas fa-arrow-left mr-2"></i>돌아가기
|
|
</button>
|
|
</div>
|
|
|
|
<div class="text-center">
|
|
<h1 class="text-3xl font-bold text-gray-900 mb-2">문서 업로드</h1>
|
|
<p class="text-gray-600">HTML 및 PDF 파일을 업로드하고 정리해보세요</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 업로드 단계 표시 -->
|
|
<div class="mb-8">
|
|
<div class="flex items-center justify-center space-x-4">
|
|
<div class="flex items-center">
|
|
<div :class="currentStep >= 1 ? 'bg-blue-600 text-white' : 'bg-gray-300 text-gray-600'"
|
|
class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium">1</div>
|
|
<span class="ml-2 text-sm font-medium" :class="currentStep >= 1 ? 'text-blue-600' : 'text-gray-500'">파일 선택</span>
|
|
</div>
|
|
<div class="w-16 h-0.5" :class="currentStep >= 2 ? 'bg-blue-600' : 'bg-gray-300'"></div>
|
|
<div class="flex items-center">
|
|
<div :class="currentStep >= 2 ? 'bg-blue-600 text-white' : 'bg-gray-300 text-gray-600'"
|
|
class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium">2</div>
|
|
<span class="ml-2 text-sm font-medium" :class="currentStep >= 2 ? 'text-blue-600' : 'text-gray-500'">서적 설정</span>
|
|
</div>
|
|
<div class="w-16 h-0.5" :class="currentStep >= 3 ? 'bg-blue-600' : 'bg-gray-300'"></div>
|
|
<div class="flex items-center">
|
|
<div :class="currentStep >= 3 ? 'bg-blue-600 text-white' : 'bg-gray-300 text-gray-600'"
|
|
class="w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium">3</div>
|
|
<span class="ml-2 text-sm font-medium" :class="currentStep >= 3 ? 'text-blue-600' : 'text-gray-500'">순서 정리</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 1단계: 파일 선택 -->
|
|
<div x-show="currentStep === 1" class="space-y-6">
|
|
<!-- 드래그 앤 드롭 영역 -->
|
|
<div class="drag-area rounded-lg p-8 text-center"
|
|
@dragover.prevent="handleDragOver"
|
|
@dragleave.prevent="handleDragLeave"
|
|
@drop.prevent="handleDrop">
|
|
<div class="mb-4">
|
|
<i class="fas fa-cloud-upload-alt text-6xl text-gray-400 mb-4"></i>
|
|
<h3 class="text-xl font-semibold text-gray-700 mb-2">파일을 드래그하여 업로드</h3>
|
|
<p class="text-gray-500 mb-4">또는 클릭하여 파일을 선택하세요</p>
|
|
</div>
|
|
|
|
<input type="file"
|
|
multiple
|
|
accept=".html,.htm,.pdf"
|
|
@change="handleFileSelect"
|
|
class="hidden"
|
|
x-ref="fileInput">
|
|
|
|
<button @click="$refs.fileInput.click()"
|
|
class="px-6 py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
|
|
<i class="fas fa-folder-open mr-2"></i>파일 선택
|
|
</button>
|
|
|
|
<p class="text-sm text-gray-400 mt-4">HTML, PDF 파일만 업로드 가능합니다</p>
|
|
</div>
|
|
|
|
<!-- 선택된 파일 목록 -->
|
|
<div x-show="selectedFiles.length > 0" class="bg-white rounded-lg shadow-sm border p-6">
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-4">
|
|
선택된 파일 (<span x-text="selectedFiles.length"></span>개)
|
|
</h3>
|
|
|
|
<div class="space-y-2 max-h-60 overflow-y-auto">
|
|
<template x-for="(file, index) in selectedFiles" :key="index">
|
|
<div class="flex items-center justify-between p-3 bg-gray-50 rounded-lg">
|
|
<div class="flex items-center space-x-3">
|
|
<i :class="file.type.includes('pdf') ? 'fas fa-file-pdf text-red-500' : 'fas fa-file-code text-blue-500'"></i>
|
|
<div>
|
|
<p class="font-medium text-gray-900" x-text="file.name"></p>
|
|
<p class="text-sm text-gray-500" x-text="formatFileSize(file.size)"></p>
|
|
</div>
|
|
</div>
|
|
<button @click="removeFile(index)"
|
|
class="p-1 text-gray-400 hover:text-red-600 transition-colors">
|
|
<i class="fas fa-times"></i>
|
|
</button>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
<div class="mt-4 flex justify-end">
|
|
<button @click="nextStep()"
|
|
class="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors">
|
|
다음 단계
|
|
<i class="fas fa-arrow-right ml-2"></i>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 2단계: 서적 설정 -->
|
|
<div x-show="currentStep === 2" class="bg-white rounded-lg shadow-sm border p-6">
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-6">서적 설정</h3>
|
|
|
|
<!-- 서적 선택 방식 -->
|
|
<div class="mb-6">
|
|
<label class="block text-sm font-medium text-gray-700 mb-3">서적 설정 방식</label>
|
|
<div class="flex space-x-4">
|
|
<label class="flex items-center">
|
|
<input type="radio" x-model="bookSelectionMode" value="existing" class="mr-2 text-blue-600">
|
|
<span class="text-sm">기존 서적에 추가</span>
|
|
</label>
|
|
<label class="flex items-center">
|
|
<input type="radio" x-model="bookSelectionMode" value="new" class="mr-2 text-blue-600">
|
|
<span class="text-sm">새 서적 생성</span>
|
|
</label>
|
|
<label class="flex items-center">
|
|
<input type="radio" x-model="bookSelectionMode" value="none" class="mr-2 text-blue-600">
|
|
<span class="text-sm">서적 없이 업로드</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 기존 서적 선택 -->
|
|
<div x-show="bookSelectionMode === 'existing'" class="mb-6">
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">기존 서적 선택</label>
|
|
<input type="text" x-model="bookSearchQuery" @input="searchBooks"
|
|
placeholder="서적 제목으로 검색..."
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
|
|
<div x-show="searchedBooks.length > 0" class="mt-2 max-h-40 overflow-y-auto border border-gray-200 rounded-md">
|
|
<template x-for="book in searchedBooks" :key="book.id">
|
|
<div @click="selectBook(book)"
|
|
class="p-3 hover:bg-gray-50 cursor-pointer border-b border-gray-100 last:border-b-0">
|
|
<p class="font-medium text-gray-900" x-text="book.title"></p>
|
|
<p class="text-sm text-gray-500" x-text="book.author"></p>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
<div x-show="selectedBook" class="mt-2 p-3 bg-blue-50 border border-blue-200 rounded-md">
|
|
<p class="font-medium text-blue-900">선택된 서적: <span x-text="selectedBook?.title"></span></p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 새 서적 생성 -->
|
|
<div x-show="bookSelectionMode === 'new'" class="mb-6 space-y-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">서적 제목 *</label>
|
|
<input type="text" x-model="newBook.title"
|
|
placeholder="서적 제목을 입력하세요"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
<p class="text-xs text-gray-500 mt-1">
|
|
💡 동일한 제목의 서적이 이미 존재하는 경우, 기존 서적에 추가할지 묻습니다.
|
|
</p>
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">저자</label>
|
|
<input type="text" x-model="newBook.author"
|
|
placeholder="저자명을 입력하세요"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
</div>
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">설명</label>
|
|
<textarea x-model="newBook.description"
|
|
placeholder="서적에 대한 설명을 입력하세요"
|
|
rows="3"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 네비게이션 버튼 -->
|
|
<div class="flex justify-between">
|
|
<button @click="prevStep()"
|
|
class="px-6 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors">
|
|
<i class="fas fa-arrow-left mr-2"></i>이전 단계
|
|
</button>
|
|
<button @click="uploadFiles()"
|
|
:disabled="uploading"
|
|
class="px-6 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-50">
|
|
<template x-if="!uploading">
|
|
<span>업로드 시작 <i class="fas fa-upload ml-2"></i></span>
|
|
</template>
|
|
<template x-if="uploading">
|
|
<span><i class="fas fa-spinner fa-spin mr-2"></i>업로드 중...</span>
|
|
</template>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 3단계: 순서 정리 (업로드 완료 후) -->
|
|
<div x-show="currentStep === 3" class="space-y-6">
|
|
<div class="bg-white rounded-lg shadow-sm border p-6">
|
|
<div class="flex justify-between items-start mb-6">
|
|
<div>
|
|
<h3 class="text-lg font-semibold text-gray-900 mb-2">문서 순서 정리</h3>
|
|
<p class="text-gray-600">드래그하거나 버튼으로 순서를 조정하고, PDF 파일을 HTML 문서와 매칭하세요</p>
|
|
</div>
|
|
<div class="flex space-x-2">
|
|
<button @click="autoSortByName()"
|
|
class="px-3 py-1 bg-blue-100 text-blue-700 rounded-md hover:bg-blue-200 transition-colors text-sm">
|
|
<i class="fas fa-sort-alpha-down mr-1"></i>이름순 정렬
|
|
</button>
|
|
<button @click="shuffleDocuments()"
|
|
class="px-3 py-1 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200 transition-colors text-sm">
|
|
<i class="fas fa-random mr-1"></i>섞기
|
|
</button>
|
|
<button @click="reverseOrder()"
|
|
class="px-3 py-1 bg-purple-100 text-purple-700 rounded-md hover:bg-purple-200 transition-colors text-sm">
|
|
<i class="fas fa-sort mr-1"></i>순서 뒤집기
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 업로드된 파일 목록 (정렬 가능) -->
|
|
<div id="sortable-list" class="space-y-4">
|
|
<template x-for="(doc, index) in uploadedDocuments" :key="doc.id">
|
|
<div class="file-item bg-gray-50 rounded-lg p-4 border border-gray-200" :data-id="doc.id">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex items-center space-x-4">
|
|
<!-- 드래그 핸들 -->
|
|
<div class="cursor-move text-gray-400 hover:text-gray-600">
|
|
<i class="fas fa-grip-vertical"></i>
|
|
</div>
|
|
|
|
<!-- 순서 번호 -->
|
|
<div class="w-8 h-8 bg-blue-600 text-white rounded-full flex items-center justify-center text-sm font-medium">
|
|
<span x-text="index + 1"></span>
|
|
</div>
|
|
|
|
<!-- 파일 정보 -->
|
|
<div class="flex-1">
|
|
<h4 class="font-medium text-gray-900" x-text="doc.title"></h4>
|
|
<p class="text-sm text-gray-500" x-text="doc.original_filename"></p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex items-center space-x-4">
|
|
<!-- 순서 조정 버튼 -->
|
|
<div class="flex flex-col space-y-1">
|
|
<button @click="moveUp(index)"
|
|
:disabled="index === 0"
|
|
class="w-6 h-6 bg-gray-200 hover:bg-gray-300 disabled:opacity-50 disabled:cursor-not-allowed rounded text-xs flex items-center justify-center transition-colors"
|
|
title="위로 이동">
|
|
<i class="fas fa-chevron-up"></i>
|
|
</button>
|
|
<button @click="moveDown(index)"
|
|
:disabled="index === uploadedDocuments.length - 1"
|
|
class="w-6 h-6 bg-gray-200 hover:bg-gray-300 disabled:opacity-50 disabled:cursor-not-allowed rounded text-xs flex items-center justify-center transition-colors"
|
|
title="아래로 이동">
|
|
<i class="fas fa-chevron-down"></i>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- PDF 매칭 (HTML 파일인 경우만) -->
|
|
<div x-show="doc.file_type === 'html'" class="flex items-center space-x-2">
|
|
<label class="text-sm text-gray-700">PDF:</label>
|
|
<select x-model="doc.matched_pdf_id"
|
|
class="px-3 py-1 border border-gray-300 rounded-md text-sm focus:outline-none focus:ring-2 focus:ring-blue-500">
|
|
<option value="">선택 안함</option>
|
|
<template x-for="pdf in pdfFiles" :key="pdf.id">
|
|
<option :value="pdf.id" x-text="pdf.title"></option>
|
|
</template>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- 완료 버튼 -->
|
|
<div class="mt-8 flex justify-between">
|
|
<button @click="resetUpload()"
|
|
class="px-6 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 transition-colors">
|
|
<i class="fas fa-redo mr-2"></i>다시 업로드
|
|
</button>
|
|
<button @click="finalizeUpload()"
|
|
:disabled="finalizing"
|
|
class="px-6 py-2 bg-green-600 text-white rounded-lg hover:bg-green-700 transition-colors disabled:opacity-50">
|
|
<template x-if="!finalizing">
|
|
<span>완료 <i class="fas fa-check ml-2"></i></span>
|
|
</template>
|
|
<template x-if="finalizing">
|
|
<span><i class="fas fa-spinner fa-spin mr-2"></i>처리 중...</span>
|
|
</template>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- JavaScript 파일들 -->
|
|
<script src="/static/js/api.js?v=2025012380"></script>
|
|
<script src="/static/js/auth.js?v=2025012351"></script>
|
|
<script src="/static/js/header-loader.js?v=2025012351"></script>
|
|
<script src="/static/js/upload.js?v=2025012390"></script>
|
|
</body>
|
|
</html>
|