Files
document-server/frontend/index.html
Hyungi Ahn 3036b8f0fb 🎉 Initial commit: Document Server MVP
 Features implemented:
- FastAPI backend with JWT authentication
- PostgreSQL database with async SQLAlchemy
- HTML document viewer with smart highlighting
- Note system connected to highlights (1:1 relationship)
- Bookmark system for quick navigation
- Integrated search (documents + notes)
- Tag system for document organization
- Docker containerization with Nginx

🔧 Technical stack:
- Backend: FastAPI + PostgreSQL + Redis
- Frontend: Alpine.js + Tailwind CSS
- Authentication: JWT tokens
- File handling: HTML + PDF support
- Search: Full-text search with relevance scoring

📋 Core functionality:
- Text selection → Highlight creation
- Highlight → Note attachment
- Note management with search/filtering
- Bookmark creation at scroll positions
- Document upload with metadata
- User management (admin creates accounts)
2025-08-21 16:09:17 +09:00

244 lines
13 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.4.0/css/all.min.css">
<link rel="stylesheet" href="/static/css/main.css">
</head>
<body class="bg-gray-50 min-h-screen">
<!-- 로그인 모달 -->
<div x-data="authModal" x-show="showLogin" x-cloak class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-white rounded-lg p-8 max-w-md w-full mx-4">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold text-gray-900">로그인</h2>
<button @click="showLogin = false" class="text-gray-400 hover:text-gray-600">
<i class="fas fa-times"></i>
</button>
</div>
<form @submit.prevent="login">
<div class="mb-4">
<label class="block text-sm font-medium text-gray-700 mb-2">이메일</label>
<input type="email" x-model="loginForm.email" required
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 class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">비밀번호</label>
<input type="password" x-model="loginForm.password" required
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 x-show="loginError" class="mb-4 p-3 bg-red-100 border border-red-400 text-red-700 rounded">
<span x-text="loginError"></span>
</div>
<button type="submit" :disabled="loginLoading"
class="w-full bg-blue-600 text-white py-2 px-4 rounded-md hover:bg-blue-700 disabled:opacity-50">
<span x-show="!loginLoading">로그인</span>
<span x-show="loginLoading">로그인 중...</span>
</button>
</form>
</div>
</div>
<!-- 메인 앱 -->
<div x-data="documentApp" x-init="init()">
<!-- 헤더 -->
<header class="bg-white shadow-sm border-b">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<div class="flex justify-between items-center h-16">
<!-- 로고 -->
<div class="flex items-center">
<h1 class="text-xl font-bold text-gray-900">
<i class="fas fa-file-alt mr-2"></i>
Document Server
</h1>
</div>
<!-- 검색바 -->
<div class="flex-1 max-w-lg mx-8" x-show="isAuthenticated">
<div class="relative">
<input type="text" x-model="searchQuery" @input="searchDocuments"
placeholder="문서, 메모 검색..."
class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
<i class="fas fa-search absolute left-3 top-3 text-gray-400"></i>
</div>
</div>
<!-- 사용자 메뉴 -->
<div class="flex items-center space-x-4">
<template x-if="!isAuthenticated">
<button @click="$refs.authModal.showLogin = true"
class="bg-blue-600 text-white px-4 py-2 rounded-md hover:bg-blue-700">
로그인
</button>
</template>
<template x-if="isAuthenticated">
<div class="flex items-center space-x-4">
<!-- 업로드 버튼 -->
<button @click="showUploadModal = true"
class="bg-green-600 text-white px-4 py-2 rounded-md hover:bg-green-700">
<i class="fas fa-upload mr-2"></i>
업로드
</button>
<!-- 사용자 드롭다운 -->
<div class="relative" x-data="{ open: false }">
<button @click="open = !open" class="flex items-center text-gray-700 hover:text-gray-900">
<i class="fas fa-user-circle text-2xl"></i>
<span x-text="user?.full_name || user?.email" class="ml-2"></span>
<i class="fas fa-chevron-down ml-1"></i>
</button>
<div x-show="open" @click.away="open = false" x-cloak
class="absolute right-0 mt-2 w-48 bg-white rounded-md shadow-lg py-1 z-50">
<a href="#" @click="showProfile = true" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-user mr-2"></i>프로필
</a>
<a href="#" @click="showMyNotes = true" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-sticky-note mr-2"></i>내 메모
</a>
<a href="#" @click="showBookmarks = true" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-bookmark mr-2"></i>책갈피
</a>
<template x-if="user?.is_admin">
<a href="#" @click="showAdmin = true" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-cog mr-2"></i>관리자
</a>
</template>
<hr class="my-1">
<a href="#" @click="logout" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<i class="fas fa-sign-out-alt mr-2"></i>로그아웃
</a>
</div>
</div>
</div>
</template>
</div>
</div>
</div>
</header>
<!-- 메인 컨텐츠 -->
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- 로그인하지 않은 경우 -->
<template x-if="!isAuthenticated">
<div class="text-center py-16">
<i class="fas fa-file-alt text-6xl text-gray-400 mb-4"></i>
<h2 class="text-3xl font-bold text-gray-900 mb-4">Document Server에 오신 것을 환영합니다</h2>
<p class="text-xl text-gray-600 mb-8">HTML 문서를 관리하고 메모, 하이라이트를 추가해보세요</p>
<button @click="$refs.authModal.showLogin = true"
class="bg-blue-600 text-white px-8 py-3 rounded-lg text-lg hover:bg-blue-700">
시작하기
</button>
</div>
</template>
<!-- 로그인한 경우 - 문서 목록 -->
<template x-if="isAuthenticated">
<div>
<!-- 필터 및 정렬 -->
<div class="flex justify-between items-center mb-6">
<div class="flex items-center space-x-4">
<h2 class="text-2xl font-bold text-gray-900">문서 목록</h2>
<select x-model="selectedTag" @change="loadDocuments"
class="px-3 py-2 border border-gray-300 rounded-md">
<option value="">모든 태그</option>
<template x-for="tag in tags" :key="tag.id">
<option :value="tag.name" x-text="tag.name"></option>
</template>
</select>
</div>
<div class="flex items-center space-x-2">
<button @click="viewMode = 'grid'"
:class="viewMode === 'grid' ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-700'"
class="px-3 py-2 rounded-md">
<i class="fas fa-th-large"></i>
</button>
<button @click="viewMode = 'list'"
:class="viewMode === 'list' ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-700'"
class="px-3 py-2 rounded-md">
<i class="fas fa-list"></i>
</button>
</div>
</div>
<!-- 문서 그리드/리스트 -->
<div x-show="viewMode === 'grid'" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<template x-for="doc in documents" :key="doc.id">
<div class="bg-white rounded-lg shadow-md hover:shadow-lg transition-shadow cursor-pointer"
@click="openDocument(doc)">
<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>
</div>
<p class="text-gray-600 text-sm mb-4 line-clamp-3" x-text="doc.description || '설명 없음'"></p>
<div class="flex flex-wrap gap-1 mb-4">
<template x-for="tag in doc.tags" :key="tag">
<span class="px-2 py-1 bg-blue-100 text-blue-800 text-xs rounded-full" x-text="tag"></span>
</template>
</div>
<div class="flex justify-between items-center text-sm text-gray-500">
<span x-text="formatDate(doc.created_at)"></span>
<span x-text="doc.uploader_name"></span>
</div>
</div>
</div>
</template>
</div>
<!-- 빈 상태 -->
<template x-if="documents.length === 0 && !loading">
<div class="text-center py-16">
<i class="fas fa-folder-open text-6xl text-gray-400 mb-4"></i>
<h3 class="text-xl font-semibold text-gray-900 mb-2">문서가 없습니다</h3>
<p class="text-gray-600 mb-6">첫 번째 문서를 업로드해보세요</p>
<button @click="showUploadModal = true"
class="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700">
<i class="fas fa-upload mr-2"></i>
문서 업로드
</button>
</div>
</template>
</div>
</template>
</main>
</div>
<!-- 인증 모달 컴포넌트 -->
<div x-data="authModal" x-ref="authModal"></div>
<!-- 스크립트 -->
<script src="/static/js/api.js"></script>
<script src="/static/js/auth.js"></script>
<script src="/static/js/main.js"></script>
<style>
[x-cloak] { display: none !important; }
.line-clamp-2 {
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.line-clamp-3 {
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
overflow: hidden;
}
</style>
</body>
</html>