✨ 주요 개선사항: - PDF API 500 에러 수정 (한글 파일명 UTF-8 인코딩 처리) - PDF 뷰어 기능 완전 구현 (PDF.js 통합, 네비게이션, 확대/축소) - 서적별 문서 그룹화 UI 데본씽크 스타일로 개선 - PDF Manager 페이지 서적별 보기 기능 추가 - Alpine.js 로드 순서 최적화로 JavaScript 에러 해결 🎨 UI/UX 개선: - 확장/축소 가능한 아코디언 스타일 서적 목록 - 간결하고 직관적인 데본씽크 스타일 인터페이스 - PDF 상태 표시 (HTML 연결, 서적 분류) - 반응형 디자인 및 부드러운 애니메이션 🔧 기술적 개선: - PDF.js 워커 설정 및 토큰 인증 처리 - 서적별 PDF 자동 그룹화 로직 - Alpine.js 컴포넌트 초기화 최적화
203 lines
9.8 KiB
HTML
203 lines
9.8 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">
|
|
|
|
<!-- Quill.js (WYSIWYG HTML 에디터) -->
|
|
<link href="https://cdn.quilljs.com/1.3.6/quill.snow.css" rel="stylesheet">
|
|
<script src="https://cdn.quilljs.com/1.3.6/quill.min.js"></script>
|
|
|
|
<style>
|
|
.ql-editor {
|
|
min-height: 400px;
|
|
font-size: 16px;
|
|
line-height: 1.6;
|
|
}
|
|
.ql-toolbar {
|
|
border-top: 1px solid #ccc;
|
|
border-left: 1px solid #ccc;
|
|
border-right: 1px solid #ccc;
|
|
}
|
|
.ql-container {
|
|
border-bottom: 1px solid #ccc;
|
|
border-left: 1px solid #ccc;
|
|
border-right: 1px solid #ccc;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body class="bg-gray-50 min-h-screen" x-data="noteEditorApp()">
|
|
<!-- 헤더 -->
|
|
<div id="header-container"></div>
|
|
|
|
<!-- 메인 컨텐츠 -->
|
|
<main class="container mx-auto px-4 py-8">
|
|
<!-- 페이지 헤더 -->
|
|
<div class="mb-8">
|
|
<div class="flex items-center justify-between mb-6">
|
|
<div>
|
|
<h1 class="text-3xl font-bold text-gray-900 flex items-center">
|
|
<i class="fas fa-edit text-blue-600 mr-3"></i>
|
|
<span x-text="isEditing ? '노트 편집' : '새 노트 작성'"></span>
|
|
</h1>
|
|
<p class="text-gray-600 mt-2">HTML 에디터로 풍부한 노트를 작성하세요</p>
|
|
</div>
|
|
|
|
<div class="flex space-x-3">
|
|
<button @click="goBack()"
|
|
class="flex items-center px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors">
|
|
<i class="fas fa-arrow-left mr-2"></i>
|
|
<span>돌아가기</span>
|
|
</button>
|
|
|
|
<button @click="saveNote()"
|
|
:disabled="saving || !noteData.title"
|
|
:class="saving || !noteData.title ? 'bg-gray-400 cursor-not-allowed' : 'bg-blue-600 hover:bg-blue-700'"
|
|
class="flex items-center px-4 py-2 text-white rounded-lg transition-colors">
|
|
<i class="fas fa-save mr-2" :class="{'fa-spin': saving}"></i>
|
|
<span x-text="saving ? '저장 중...' : '저장'"></span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 노트 설정 -->
|
|
<div class="bg-white rounded-lg shadow-sm border p-6 mb-6">
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
|
<!-- 제목 -->
|
|
<div class="md:col-span-2">
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">제목 *</label>
|
|
<input type="text"
|
|
x-model="noteData.title"
|
|
placeholder="노트 제목을 입력하세요..."
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
required>
|
|
</div>
|
|
|
|
<!-- 노트북 선택 -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">노트북</label>
|
|
<select x-model="noteData.notebook_id"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
|
<option value="">미분류</option>
|
|
<template x-for="notebook in availableNotebooks" :key="notebook.id">
|
|
<option :value="notebook.id" x-text="notebook.title"></option>
|
|
</template>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6">
|
|
<!-- 노트 타입 -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">타입</label>
|
|
<select x-model="noteData.note_type"
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
|
<option value="note">일반 노트</option>
|
|
<option value="research">연구 노트</option>
|
|
<option value="summary">요약</option>
|
|
<option value="idea">아이디어</option>
|
|
<option value="guide">가이드</option>
|
|
<option value="reference">참고 자료</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-6">
|
|
<!-- 태그 -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">태그</label>
|
|
<input type="text"
|
|
x-model="tagInput"
|
|
@keydown.enter.prevent="addTag()"
|
|
placeholder="태그를 입력하고 Enter를 누르세요..."
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent">
|
|
|
|
<!-- 태그 목록 -->
|
|
<div x-show="noteData.tags.length > 0" class="mt-3 flex flex-wrap gap-2">
|
|
<template x-for="(tag, index) in noteData.tags" :key="index">
|
|
<span class="inline-flex items-center px-3 py-1 bg-blue-100 text-blue-800 text-sm rounded-full">
|
|
<span x-text="tag"></span>
|
|
<button @click="removeTag(index)" class="ml-2 text-blue-600 hover:text-blue-800">
|
|
<i class="fas fa-times text-xs"></i>
|
|
</button>
|
|
</span>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 공개 설정 -->
|
|
<div>
|
|
<label class="block text-sm font-medium text-gray-700 mb-2">공개 설정</label>
|
|
<div class="flex items-center space-x-4">
|
|
<label class="flex items-center">
|
|
<input type="radio"
|
|
x-model="noteData.is_published"
|
|
:value="false"
|
|
class="mr-2 text-blue-600">
|
|
<span class="text-sm text-gray-700">초안</span>
|
|
</label>
|
|
<label class="flex items-center">
|
|
<input type="radio"
|
|
x-model="noteData.is_published"
|
|
:value="true"
|
|
class="mr-2 text-blue-600">
|
|
<span class="text-sm text-gray-700">공개</span>
|
|
</label>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- HTML 에디터 -->
|
|
<div class="bg-white rounded-lg shadow-sm border overflow-hidden">
|
|
<div class="p-4 border-b bg-gray-50">
|
|
<div class="flex items-center justify-between">
|
|
<h3 class="text-lg font-semibold text-gray-900">노트 내용</h3>
|
|
<div class="flex items-center space-x-4">
|
|
<button @click="toggleEditorMode()"
|
|
class="text-sm text-gray-600 hover:text-gray-800">
|
|
<i class="fas fa-code mr-1"></i>
|
|
<span x-text="editorMode === 'wysiwyg' ? 'HTML 코드' : 'WYSIWYG'"></span>
|
|
</button>
|
|
<span class="text-sm text-gray-500" x-text="getWordCount() + '자'"></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- WYSIWYG 에디터 -->
|
|
<div x-show="editorMode === 'wysiwyg'" id="quill-editor"></div>
|
|
|
|
<!-- HTML 코드 에디터 -->
|
|
<div x-show="editorMode === 'html'" class="p-4">
|
|
<textarea x-model="noteData.content"
|
|
rows="20"
|
|
placeholder="HTML 코드를 직접 입력하세요..."
|
|
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent font-mono text-sm"
|
|
style="resize: vertical;"></textarea>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 미리보기 -->
|
|
<div x-show="noteData.content" class="mt-6 bg-white rounded-lg shadow-sm border">
|
|
<div class="p-4 border-b bg-gray-50">
|
|
<h3 class="text-lg font-semibold text-gray-900">미리보기</h3>
|
|
</div>
|
|
<div class="p-6">
|
|
<div x-html="noteData.content" class="prose max-w-none"></div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- JavaScript 파일들 -->
|
|
<script src="/static/js/header-loader.js?v=2025012603"></script>
|
|
<script src="/static/js/api.js?v=2025012607"></script>
|
|
<script src="/static/js/auth.js?v=2025012351"></script>
|
|
<script src="/static/js/note-editor.js?v=2025012608"></script>
|
|
</body>
|
|
</html>
|