하이라이트 색상 문제 해결 및 다중 하이라이트 렌더링 개선

주요 수정사항:
- 하이라이트 생성 시 color → highlight_color 필드명 수정으로 색상 전달 문제 해결
- 분홍색을 더 연하게 변경하여 글씨 가독성 향상
- 다중 하이라이트 렌더링을 위아래 균등 분할로 개선
- CSS highlight-span 클래스 추가 및 색상 적용 강화
- 하이라이트 생성/렌더링 과정에 상세한 디버깅 로그 추가

UI 개선:
- 단일 하이라이트: 선택한 색상으로 정확히 표시
- 다중 하이라이트: 위아래로 균등하게 색상 분할 표시
- 메모 입력 모달에서 선택된 텍스트 표시 개선

버그 수정:
- 프론트엔드-백엔드 API 스키마 불일치 해결
- CSS 스타일 우선순위 문제 해결
- 하이라이트 색상이 노랑색으로만 표시되던 문제 해결
This commit is contained in:
Hyungi Ahn
2025-08-28 07:13:00 +09:00
parent 3e0a03f149
commit 5d4465b15c
18 changed files with 5569 additions and 648 deletions

View File

@@ -12,6 +12,20 @@
<link rel="stylesheet" href="/static/css/viewer.css">
</head>
<body class="bg-gray-50 min-h-screen">
<!-- 인증 확인 및 리다이렉트 -->
<script>
// 페이지 로드 시 인증 상태 확인
(function() {
const token = localStorage.getItem('access_token');
if (!token) {
console.log('🔐 인증 토큰이 없습니다. 메인 페이지로 리다이렉트합니다.');
window.location.href = '/';
return;
}
console.log('✅ 인증 토큰 확인됨:', token.substring(0, 20) + '...');
})();
</script>
<div x-data="documentViewer" x-init="init()">
<!-- 헤더 - 투명하고 세련된 3줄 디자인 -->
<header class="bg-white/80 backdrop-blur-md shadow-lg border-b border-white/20 sticky top-0 z-50 w-full">
@@ -108,9 +122,9 @@
:class="selectedHighlightColor === '#90EE90' ? 'ring-2 ring-green-400 scale-110' : 'hover:scale-110'"
class="w-6 h-6 bg-gradient-to-br from-green-300 to-green-400 rounded-full border border-white shadow-sm transition-all duration-200"
title="초록색"></button>
<button @click="createHighlightWithColor('#FFB6C1')"
:class="selectedHighlightColor === '#FFB6C1' ? 'ring-2 ring-pink-400 scale-110' : 'hover:scale-110'"
class="w-6 h-6 bg-gradient-to-br from-pink-300 to-pink-400 rounded-full border border-white shadow-sm transition-all duration-200"
<button @click="createHighlightWithColor('#FFCCCB')"
:class="selectedHighlightColor === '#FFCCCB' ? 'ring-2 ring-pink-400 scale-110' : 'hover:scale-110'"
class="w-6 h-6 bg-gradient-to-br from-pink-200 to-pink-300 rounded-full border border-white shadow-sm transition-all duration-200"
title="분홍색"></button>
<button @click="createHighlightWithColor('#87CEEB')"
:class="selectedHighlightColor === '#87CEEB' ? 'ring-2 ring-blue-400 scale-110' : 'hover:scale-110'"
@@ -209,6 +223,11 @@
<i class="fas fa-eye text-blue-500"></i>
<span>메모 보기</span>
</button>
<button @click="activateNoteMode(); activeFeatureMenu = null"
class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-green-100 rounded-lg flex items-center space-x-2 transition-colors">
<i class="fas fa-plus text-green-500"></i>
<span>메모 만들기</span>
</button>
</div>
</div>
@@ -459,33 +478,8 @@
<p x-show="filteredDocuments.length > 0" class="text-xs text-gray-500 mt-1" x-text="`${filteredDocuments.length}개 문서`"></p>
</div>
<!-- 링크 타입 선택 -->
<!-- 대상 텍스트 선택 (무조건 텍스트 선택만 지원) -->
<div class="mb-6">
<label class="block text-sm font-semibold text-gray-700 mb-3">링크 타입</label>
<div class="grid grid-cols-2 gap-3">
<button @click="linkForm.link_type = 'document'"
:class="linkForm.link_type === 'document' ? 'bg-purple-100 border-purple-500 text-purple-700' : 'bg-gray-50 border-gray-300 text-gray-600'"
class="p-4 border-2 rounded-lg transition-all duration-200 text-left">
<div class="flex items-center space-x-2 mb-2">
<i class="fas fa-file-alt"></i>
<span class="font-semibold">문서 전체</span>
</div>
<p class="text-xs">문서 전체로 이동</p>
</button>
<button @click="linkForm.link_type = 'text_fragment'"
:class="linkForm.link_type === 'text_fragment' ? 'bg-purple-100 border-purple-500 text-purple-700' : 'bg-gray-50 border-gray-300 text-gray-600'"
class="p-4 border-2 rounded-lg transition-all duration-200 text-left">
<div class="flex items-center space-x-2 mb-2">
<i class="fas fa-crosshairs"></i>
<span class="font-semibold">특정 텍스트</span>
</div>
<p class="text-xs">문서 내 특정 부분으로 이동</p>
</button>
</div>
</div>
<!-- 특정 텍스트 선택 (text_fragment 타입일 때만) -->
<div x-show="linkForm.link_type === 'text_fragment'" class="mb-6">
<label class="block text-sm font-semibold text-gray-700 mb-2">대상 텍스트</label>
<div class="space-y-3">
<button @click="openTargetDocumentSelector()"
@@ -599,6 +593,69 @@
</div>
</div>
<!-- 메모 입력 모달 (하이라이트 생성 후) -->
<div x-show="showNoteInputModal"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
class="fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4"
@click="showNoteInputModal = false">
<div @click.stop class="bg-white rounded-2xl shadow-2xl max-w-md w-full">
<div class="flex justify-between items-center p-6 border-b">
<h3 class="text-xl font-bold text-gray-800 flex items-center space-x-2">
<i class="fas fa-sticky-note text-blue-600"></i>
<span>메모 추가</span>
</h3>
<button @click="showNoteInputModal = false"
class="w-8 h-8 bg-gray-100 hover:bg-gray-200 rounded-full flex items-center justify-center transition-colors">
<i class="fas fa-times text-gray-600"></i>
</button>
</div>
<div class="p-6">
<!-- 선택된 텍스트 표시 -->
<div class="bg-blue-50 rounded-lg p-3 mb-4">
<p class="text-sm text-gray-600 mb-1">선택된 텍스트:</p>
<p class="text-blue-800 font-medium" x-text="selectedText"></p>
</div>
<!-- 메모 입력 질문 -->
<p class="text-gray-700 mb-4">이 하이라이트에 메모를 추가하시겠습니까?</p>
<!-- 메모 입력 칸 -->
<textarea x-model="noteForm.content"
placeholder="메모 내용을 입력하세요..."
class="w-full h-32 p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 resize-none"
@keydown.ctrl.enter="createNoteForHighlight()"
@keydown.meta.enter="createNoteForHighlight()"></textarea>
<!-- 태그 입력 (선택사항) -->
<input x-model="noteForm.tags"
type="text"
placeholder="태그 (선택사항, 쉼표로 구분)"
class="w-full mt-3 p-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500">
<!-- 버튼들 -->
<div class="flex justify-end space-x-3 mt-6">
<button @click="skipNoteForHighlight()"
class="px-4 py-2 text-gray-600 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors">
나중에 입력
</button>
<button @click="createNoteForHighlight()"
:disabled="!noteForm.content.trim()"
:class="noteForm.content.trim() ? 'bg-blue-600 hover:bg-blue-700' : 'bg-gray-300 cursor-not-allowed'"
class="px-6 py-2 text-white rounded-lg transition-colors">
확인
</button>
</div>
</div>
</div>
</div>
<!-- 책갈피 모달 -->
<div x-show="showBookmarksModal"
x-transition:enter="transition ease-out duration-300"
@@ -739,36 +796,108 @@
<!-- 스크립트 -->
<script src="/static/js/api.js?v=2025012614"></script>
<script src="/static/js/viewer.js?v=2025012641"></script>
<!-- 캐시 및 성능 최적화 시스템 -->
<script src="/static/js/viewer/utils/cache-manager.js?v=2025012607"></script>
<script src="/static/js/viewer/utils/cached-api.js?v=2025012607"></script>
<script src="/static/js/viewer/utils/module-loader.js?v=2025012607"></script>
<!-- 모든 모듈들 직접 로드 -->
<script src="/static/js/viewer/core/document-loader.js?v=2025012607"></script>
<script src="/static/js/viewer/features/ui-manager.js?v=2025012607"></script>
<script src="/static/js/viewer/features/highlight-manager.js?v=2025012607"></script>
<script src="/static/js/viewer/features/link-manager.js?v=2025012607"></script>
<script src="/static/js/viewer/features/bookmark-manager.js?v=2025012607"></script>
<!-- ViewerCore (Alpine.js 컴포넌트) -->
<script src="/static/js/viewer/viewer-core.js?v=2025012607"></script>
<!-- Alpine.js 프레임워크 -->
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
<style>
[x-cloak] { display: none !important; }
/* 링크 강제 스타일 */
/* 링크된 텍스트 하이라이트 (URL에서 온 것) - 레이아웃 안전 */
.linked-text-highlight {
background-color: #FEF3C7 !important;
border: 1px solid #F59E0B !important;
border-radius: 2px !important;
padding: 0 1px !important;
display: inline !important;
box-decoration-break: clone !important;
-webkit-box-decoration-break: clone !important;
line-height: inherit !important;
vertical-align: baseline !important;
margin: 0 !important;
box-sizing: border-box !important;
}
/* 백링크 강제 스타일 - 레이아웃 안전 */
.backlink-highlight {
color: #EA580C !important;
background-color: rgba(234, 88, 12, 0.3) !important;
border: 3px solid #EA580C !important;
border-radius: 6px !important;
padding: 6px 8px !important;
background-color: rgba(234, 88, 12, 0.2) !important;
border: 1px solid #EA580C !important;
border-radius: 3px !important;
padding: 0 2px !important;
font-weight: bold !important;
display: inline !important;
box-decoration-break: clone !important;
-webkit-box-decoration-break: clone !important;
line-height: inherit !important;
vertical-align: baseline !important;
margin: 0 !important;
box-sizing: border-box !important;
text-decoration: underline !important;
cursor: pointer !important;
box-shadow: 0 4px 8px rgba(234, 88, 12, 0.4) !important;
display: inline-block !important;
margin: 2px !important;
}
/* 링크 스타일 */
.document-link {
color: #7C3AED !important;
background-color: rgba(124, 58, 237, 0.1) !important;
border-radius: 2px !important;
padding: 0 1px !important;
display: inline !important;
line-height: inherit !important;
vertical-align: baseline !important;
margin: 0 !important;
box-sizing: border-box !important;
text-decoration: underline !important;
cursor: pointer !important;
}
/* 겹치는 영역 처리 - 백링크 안에 링크가 있는 경우 */
.backlink-highlight .document-link {
background: linear-gradient(to bottom,
rgba(234, 88, 12, 0.3) 0%,
rgba(234, 88, 12, 0.3) 50%,
rgba(124, 58, 237, 0.2) 50%,
rgba(124, 58, 237, 0.2) 100%) !important;
border-top: 1px solid #EA580C !important;
border-bottom: 1px solid #7C3AED !important;
}
/* 겹치는 영역 처리 - 링크 안에 백링크가 있는 경우 */
.document-link .backlink-highlight {
background: linear-gradient(to bottom,
rgba(124, 58, 237, 0.2) 0%,
rgba(124, 58, 237, 0.2) 50%,
rgba(234, 88, 12, 0.3) 50%,
rgba(234, 88, 12, 0.3) 100%) !important;
border-top: 1px solid #7C3AED !important;
border-bottom: 1px solid #EA580C !important;
}
/* 하이라이트 스타일 개선 */
.highlight {
.highlight, .highlight-span {
padding: 1px 2px;
border-radius: 2px;
cursor: pointer;
transition: all 0.2s ease;
}
.highlight:hover {
.highlight:hover, .highlight-span:hover {
box-shadow: 0 0 4px rgba(0,0,0,0.3);
transform: scale(1.02);
}