feat: 플로팅 메모창 구현 및 문서 삭제 기능 안정화

 새로운 기능:
- 플로팅 메모창: 드래그 가능한 독립적인 메모/하이라이트 창
- 크기 조절: 마우스로 창 크기 자유롭게 조정
- 위치 이동: 헤더 드래그로 원하는 위치에 배치
- 메모 검색: 실시간 메모 내용 검색 및 필터링
- 하이라이트 이동: 메모창에서 문서 내 하이라이트 위치로 스크롤

🛠️ 개선사항:
- 문서 삭제 안정화: 외래키 제약조건 해결 (Note->Highlight->Document 순서)
- 레이아웃 개선: 문서 뷰어가 전체 화면 사용 가능
- 사용자 경험: 사이드바 대신 플로팅 윈도우로 더 유연한 UI
- 캐시 버스팅: JavaScript 파일 버전 관리 개선

🐛 버그 수정:
- 중복 스크립트 로드 문제 해결
- 하이라이트 메모 업데이트 API 오류 수정
- 문서 삭제 시 500 에러 해결
This commit is contained in:
Hyungi Ahn
2025-08-23 15:00:01 +09:00
parent 46546da55f
commit c7f55ac50d
6 changed files with 362 additions and 30 deletions

View File

@@ -144,7 +144,7 @@
<!-- 메인 컨테이너 -->
<div class="flex h-screen pt-16">
<!-- 사이드바 -->
<!-- 왼쪽 사이드바 (항상 표시) -->
<aside class="w-80 bg-white shadow-lg border-r flex flex-col">
<!-- 사이드바 헤더 -->
<div class="p-4 border-b bg-gray-50">
@@ -369,8 +369,8 @@
<!-- 메인 콘텐츠 영역 -->
<main class="flex-1 flex flex-col">
<!-- 콘텐츠 헤더 -->
<div class="bg-white border-b p-4">
<!-- 콘텐츠 헤더 -->
<div class="bg-white border-b p-4">
<div class="flex items-center justify-between">
<div>
<h2 class="text-lg font-semibold text-gray-900" x-text="selectedDocument ? selectedDocument.title : '문서를 선택하세요'"></h2>
@@ -378,6 +378,17 @@
</div>
<div x-show="selectedDocument" class="flex space-x-2">
<!-- 플로팅 메모창 토글 -->
<button
@click="showFloatingMemo = !showFloatingMemo"
class="px-3 py-2 rounded transition-colors"
:class="showFloatingMemo ? 'bg-blue-500 text-white' : 'text-blue-600 border border-blue-600 hover:bg-blue-50'"
title="메모 & 하이라이트 창"
>
<i class="fas fa-window-restore mr-1"></i>
메모창 (<span x-text="notes.length + highlights.length"></span>)
</button>
<button
@click="toggleHighlightMode()"
class="px-3 py-2 rounded transition-colors"
@@ -441,9 +452,154 @@
<button onclick="window.hierarchyInstance.createHighlight('red')" class="w-8 h-8 bg-red-300 rounded hover:bg-red-400" title="빨강"></button>
</div>
</div>
</div>
</main>
</div>
<!-- 플로팅 메모창 -->
<div
x-show="showFloatingMemo && selectedDocument"
class="fixed bg-white rounded-lg shadow-2xl border z-40 flex flex-col"
:style="`left: ${floatingMemoPosition.x}px; top: ${floatingMemoPosition.y}px; width: ${floatingMemoSize.width}px; height: ${floatingMemoSize.height}px;`"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
>
<!-- 플로팅 창 헤더 (드래그 가능) -->
<div
class="flex items-center justify-between p-3 bg-gradient-to-r from-blue-500 to-blue-600 text-white rounded-t-lg cursor-move"
@mousedown="startDragging($event)"
>
<div class="flex items-center space-x-2">
<i class="fas fa-window-restore"></i>
<h3 class="font-semibold">📝 메모 & 하이라이트</h3>
<div class="flex space-x-1">
<span class="text-xs bg-white bg-opacity-20 px-2 py-1 rounded-full" x-text="`${notes.length}개 메모`"></span>
<span class="text-xs bg-white bg-opacity-20 px-2 py-1 rounded-full" x-text="`${highlights.length}개 하이라이트`"></span>
</div>
</div>
<div class="flex items-center space-x-2">
<!-- 크기 조절 버튼들 -->
<button
@click="toggleFloatingMemoSize()"
class="text-white hover:bg-white hover:bg-opacity-20 p-1 rounded"
title="크기 조절"
>
<i class="fas fa-expand-arrows-alt text-sm"></i>
</button>
<!-- 닫기 버튼 -->
<button
@click="showFloatingMemo = false"
class="text-white hover:bg-white hover:bg-opacity-20 p-1 rounded"
title="닫기"
>
<i class="fas fa-times"></i>
</button>
</div>
</div>
<!-- 메모 검색 -->
<div class="p-3 border-b bg-gray-50">
<div class="relative">
<input
type="text"
x-model="memoSearchQuery"
@input="filterMemos()"
placeholder="메모 검색..."
class="w-full pl-8 pr-4 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<i class="fas fa-search absolute left-2.5 top-2.5 text-gray-400 text-sm"></i>
</div>
</div>
<!-- 메모 목록 -->
<div class="flex-1 overflow-y-auto p-3">
<div x-show="(filteredNotes.length === 0 && highlights.length === 0)" class="text-center text-gray-500 py-8">
<i class="fas fa-sticky-note text-3xl text-gray-300 mb-2"></i>
<p class="text-sm">아직 메모나 하이라이트가 없습니다.</p>
<p class="text-xs text-gray-400 mt-1">문서에서 텍스트를 선택하여 하이라이트를 만들어보세요.</p>
</div>
<!-- 하이라이트 섹션 -->
<div x-show="highlights.length > 0" class="mb-4">
<h4 class="font-medium text-gray-700 mb-3 flex items-center">
<i class="fas fa-highlighter text-yellow-500 mr-2"></i>
하이라이트 (<span x-text="highlights.length"></span>)
</h4>
<template x-for="highlight in highlights" :key="highlight.id">
<div class="mb-3 p-3 bg-gray-50 rounded-lg border-l-4" :class="{
'border-yellow-400': highlight.highlight_color === 'yellow',
'border-blue-400': highlight.highlight_color === 'blue',
'border-green-400': highlight.highlight_color === 'green',
'border-red-400': highlight.highlight_color === 'red'
}">
<div class="text-sm text-gray-600 mb-1" x-text="highlight.selected_text"></div>
<div x-show="highlight.notes && highlight.notes.content" class="text-xs text-gray-500 mt-2 p-2 bg-white rounded border">
<i class="fas fa-comment mr-1"></i>
<span x-text="highlight.notes.content"></span>
</div>
<div class="flex items-center justify-between mt-2">
<span class="text-xs text-gray-400" x-text="new Date(highlight.created_at).toLocaleDateString()"></span>
<div class="flex space-x-1">
<button
@click="scrollToHighlight(highlight.id)"
class="text-xs text-blue-600 hover:text-blue-800 p-1"
title="하이라이트로 이동"
>
<i class="fas fa-arrow-right"></i>
</button>
<button
@click="deleteHighlight(highlight.id)"
class="text-xs text-red-600 hover:text-red-800 p-1"
title="삭제"
>
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</div>
</template>
</div>
<!-- 독립 메모 섹션 -->
<div x-show="filteredNotes.length > 0">
<h4 class="font-medium text-gray-700 mb-3 flex items-center">
<i class="fas fa-sticky-note text-blue-500 mr-2"></i>
독립 메모 (<span x-text="filteredNotes.length"></span>)
</h4>
<template x-for="note in filteredNotes" :key="note.id">
<div class="mb-3 p-3 bg-blue-50 rounded-lg border-l-4 border-blue-400">
<div class="text-sm text-gray-700" x-text="note.content"></div>
<div class="flex items-center justify-between mt-2">
<span class="text-xs text-gray-400" x-text="new Date(note.created_at).toLocaleDateString()"></span>
<button
@click="deleteNote(note.id)"
class="text-xs text-red-600 hover:text-red-800 p-1"
title="삭제"
>
<i class="fas fa-trash"></i>
</button>
</div>
</div>
</template>
</div>
</div>
<!-- 크기 조절 핸들 -->
<div
class="absolute bottom-0 right-0 w-4 h-4 cursor-se-resize bg-gray-300 hover:bg-gray-400"
@mousedown="startResizing($event)"
title="크기 조절"
>
<i class="fas fa-grip-lines text-xs text-gray-600 absolute bottom-0.5 right-0.5"></i>
</div>
</div>
<!-- 로그인 모달 -->
<div x-data="authModal()" x-show="showLoginModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<div class="bg-white rounded-lg p-6 w-96 max-w-md mx-4">