링크/백링크 기능 수정 및 안정화
- 링크 생성 기능 완전 복구
- createDocumentLink 함수를 Alpine.js 데이터 객체 내로 이동
- API 필드명 불일치 수정 (source_text → selected_text 등)
- 필수 필드 검증 및 상세 에러 로깅 추가
- API 엔드포인트 수정
- 백엔드와 일치하도록 /documents/{id}/links, /documents/{id}/backlinks 사용
- 올바른 매개변수 전달 방식 적용
- 링크/백링크 렌더링 안정화
- forEach 오류 방지를 위한 배열 타입 검증
- 데이터 없을 시 기존 링크 유지 로직
- 안전한 초기화 및 에러 처리
- UI 단순화
- 같은 서적/다른 서적 구분 제거
- 서적 선택 → 문서 선택 단순한 2단계 프로세스
- 백링크는 자동 생성되도록 수정
- 디버깅 로그 대폭 강화
- API 호출, 응답, 렌더링 각 단계별 상세 로깅
- 데이터 타입 및 개수 추적
This commit is contained in:
@@ -26,7 +26,7 @@
|
||||
})();
|
||||
</script>
|
||||
|
||||
<div x-data="documentViewer" x-init="init()">
|
||||
<div x-data="documentViewer" x-init="init(); $store.documentViewer.init()">
|
||||
<!-- 헤더 - 투명하고 세련된 3줄 디자인 -->
|
||||
<header class="bg-white/80 backdrop-blur-md shadow-lg border-b border-white/20 sticky top-0 z-50 w-full">
|
||||
<div class="w-full px-6 py-4">
|
||||
@@ -134,67 +134,26 @@
|
||||
|
||||
<!-- 중앙: 기능 버튼들 -->
|
||||
<div class="flex items-center space-x-3">
|
||||
<!-- 링크 버튼 그룹 -->
|
||||
<div class="relative">
|
||||
<button @click="toggleFeatureMenu('link')"
|
||||
:class="activeFeatureMenu === 'link' ? 'bg-purple-600' : 'bg-purple-500/80 hover:bg-purple-500'"
|
||||
class="px-4 py-2 text-white rounded-xl transition-all duration-200 flex items-center space-x-2 shadow-sm">
|
||||
<i class="fas fa-link text-sm"></i>
|
||||
<span class="text-sm font-medium">링크</span>
|
||||
<span x-show="documentLinks.length > 0"
|
||||
class="bg-white text-purple-600 text-xs rounded-full w-5 h-5 flex items-center justify-center font-bold"
|
||||
x-text="documentLinks.length"></span>
|
||||
<i class="fas fa-chevron-down text-xs ml-1" :class="activeFeatureMenu === 'link' ? 'rotate-180' : ''"></i>
|
||||
</button>
|
||||
<!-- 링크 서브메뉴 -->
|
||||
<div x-show="activeFeatureMenu === 'link'"
|
||||
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"
|
||||
class="absolute top-full left-0 mt-2 bg-white/90 backdrop-blur-md rounded-xl shadow-lg border border-white/30 p-2 z-10 min-w-max">
|
||||
<button @click="showLinksModal = true; activeFeatureMenu = null"
|
||||
class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-purple-100 rounded-lg flex items-center space-x-2 transition-colors">
|
||||
<i class="fas fa-eye text-purple-500"></i>
|
||||
<span>링크 보기</span>
|
||||
</button>
|
||||
<button @click="console.log('링크 만들기 클릭됨'); activateLinkMode(); activeFeatureMenu = null"
|
||||
class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-purple-100 rounded-lg flex items-center space-x-2 transition-colors">
|
||||
<i class="fas fa-plus text-purple-500"></i>
|
||||
<span>링크 만들기</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 링크 버튼 - 드래그 후 클릭으로 링크 생성 -->
|
||||
<button @click="activateLinkMode()"
|
||||
:class="activeMode === 'link' ? 'bg-purple-600' : 'bg-purple-500/80 hover:bg-purple-500'"
|
||||
class="px-4 py-2 text-white rounded-xl transition-all duration-200 flex items-center space-x-2 shadow-sm"
|
||||
title="텍스트를 드래그한 후 이 버튼을 클릭하여 링크를 생성하세요">
|
||||
<i class="fas fa-link text-sm"></i>
|
||||
<span class="text-sm font-medium">링크</span>
|
||||
<span x-show="documentLinks.length > 0"
|
||||
class="bg-white text-purple-600 text-xs rounded-full w-5 h-5 flex items-center justify-center font-bold"
|
||||
x-text="documentLinks.length"></span>
|
||||
</button>
|
||||
|
||||
<!-- 백링크 버튼 그룹 -->
|
||||
<div class="relative">
|
||||
<button @click="toggleFeatureMenu('backlink')"
|
||||
:class="activeFeatureMenu === 'backlink' ? 'bg-orange-600' : 'bg-orange-500/80 hover:bg-orange-500'"
|
||||
class="px-4 py-2 text-white rounded-xl transition-all duration-200 flex items-center space-x-2 shadow-sm">
|
||||
<i class="fas fa-arrow-left text-sm"></i>
|
||||
<span class="text-sm font-medium">백링크</span>
|
||||
<span x-show="backlinks.length > 0"
|
||||
class="bg-white text-orange-600 text-xs rounded-full w-5 h-5 flex items-center justify-center font-bold"
|
||||
x-text="backlinks.length"></span>
|
||||
<i class="fas fa-chevron-down text-xs ml-1" :class="activeFeatureMenu === 'backlink' ? 'rotate-180' : ''"></i>
|
||||
</button>
|
||||
<!-- 백링크 서브메뉴 -->
|
||||
<div x-show="activeFeatureMenu === 'backlink'"
|
||||
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"
|
||||
class="absolute top-full left-0 mt-2 bg-white/90 backdrop-blur-md rounded-xl shadow-lg border border-white/30 p-2 z-10 min-w-max">
|
||||
<button @click="showBacklinksModal = true; loadBacklinks(); activeFeatureMenu = null"
|
||||
class="w-full px-3 py-2 text-sm text-gray-700 hover:bg-orange-100 rounded-lg flex items-center space-x-2 transition-colors">
|
||||
<i class="fas fa-eye text-orange-500"></i>
|
||||
<span>백링크 보기</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- 백링크 표시 (읽기 전용) -->
|
||||
<div class="px-4 py-2 bg-orange-500/80 text-white rounded-xl flex items-center space-x-2 shadow-sm"
|
||||
title="이 문서를 참조하는 다른 문서들">
|
||||
<i class="fas fa-arrow-left text-sm"></i>
|
||||
<span class="text-sm font-medium">백링크</span>
|
||||
<span x-show="backlinks.length > 0"
|
||||
class="bg-white text-orange-600 text-xs rounded-full w-5 h-5 flex items-center justify-center font-bold"
|
||||
x-text="backlinks.length"></span>
|
||||
</div>
|
||||
|
||||
<!-- 메모 버튼 그룹 -->
|
||||
@@ -268,14 +227,14 @@
|
||||
|
||||
<!-- 오른쪽: 액션 버튼들 -->
|
||||
<div class="flex items-center space-x-3">
|
||||
<button @click="downloadOriginalFile()"
|
||||
<button @click="$store.documentViewer.downloadOriginalFile()"
|
||||
class="bg-red-500/80 hover:bg-red-500 text-white px-4 py-2 rounded-xl transition-all duration-200 flex items-center space-x-2 shadow-sm"
|
||||
title="원본 PDF 다운로드">
|
||||
<i class="fas fa-file-pdf text-sm"></i>
|
||||
<span class="text-sm font-medium">PDF</span>
|
||||
</button>
|
||||
|
||||
<button @click="toggleLanguage()"
|
||||
<button @click="$store.documentViewer.toggleLanguage()"
|
||||
class="bg-blue-500/80 hover:bg-blue-500 text-white px-4 py-2 rounded-xl transition-all duration-200 flex items-center space-x-2 shadow-sm"
|
||||
id="language-toggle-btn">
|
||||
<i class="fas fa-globe text-sm"></i>
|
||||
@@ -417,37 +376,14 @@
|
||||
<p class="text-purple-700" x-text="linkForm?.selected_text || ''"></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.book_scope = 'same'; resetTargetSelection()"
|
||||
:class="linkForm.book_scope === 'same' ? 'bg-green-100 border-green-500 text-green-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-book"></i>
|
||||
<span class="font-semibold">같은 서적</span>
|
||||
</div>
|
||||
<p class="text-xs">현재 서적 내 문서</p>
|
||||
</button>
|
||||
<button @click="linkForm.book_scope = 'other'; resetTargetSelection()"
|
||||
:class="linkForm.book_scope === 'other' ? 'bg-blue-100 border-blue-500 text-blue-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-books"></i>
|
||||
<span class="font-semibold">다른 서적</span>
|
||||
</div>
|
||||
<p class="text-xs">다른 서적의 문서</p>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 대상 서적 선택 (다른 서적인 경우만) -->
|
||||
<div x-show="linkForm.book_scope === 'other'" class="mb-6">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">대상 서적</label>
|
||||
<select x-model="linkForm.target_book_id"
|
||||
@change="loadDocumentsFromBook()"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">서적 선택</label>
|
||||
<select
|
||||
x-model="linkForm.target_book_id"
|
||||
@change="loadDocumentsFromBook()"
|
||||
class="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 bg-white"
|
||||
>
|
||||
<option value="">서적을 선택하세요</option>
|
||||
<template x-for="book in availableBooks" :key="book.id">
|
||||
<option :value="book.id" x-text="book.title"></option>
|
||||
@@ -455,21 +391,21 @@
|
||||
</select>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<!-- 대상 문서 선택 -->
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">
|
||||
대상 문서
|
||||
<span x-show="linkForm.book_scope === 'same'" class="text-green-600 text-xs">(현재 서적)</span>
|
||||
<span x-show="linkForm.book_scope === 'other' && linkForm.target_book_id" class="text-blue-600 text-xs" x-text="`(${getSelectedBookTitle()})`"></span>
|
||||
<span x-show="linkForm.target_book_id" class="text-blue-600 text-xs" x-text="`(${getSelectedBookTitle()})`"></span>
|
||||
</label>
|
||||
<select x-model="linkForm.target_document_id"
|
||||
@change="onTargetDocumentChange()"
|
||||
:disabled="linkForm.book_scope === 'other' && !linkForm.target_book_id"
|
||||
:disabled="!linkForm.target_book_id"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 disabled:bg-gray-100 disabled:cursor-not-allowed">
|
||||
<option value="">
|
||||
<span x-show="linkForm.book_scope === 'same'">문서를 선택하세요</span>
|
||||
<span x-show="linkForm.book_scope === 'other' && !linkForm.target_book_id">먼저 서적을 선택하세요</span>
|
||||
<span x-show="linkForm.book_scope === 'other' && linkForm.target_book_id">문서를 선택하세요</span>
|
||||
<span x-show="!linkForm.target_book_id">먼저 서적을 선택하세요</span>
|
||||
<span x-show="linkForm.target_book_id">문서를 선택하세요</span>
|
||||
</option>
|
||||
<template x-for="doc in filteredDocuments" :key="doc.id">
|
||||
<option :value="doc.id" x-text="doc.title"></option>
|
||||
@@ -482,7 +418,7 @@
|
||||
<div class="mb-6">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-2">대상 텍스트</label>
|
||||
<div class="space-y-3">
|
||||
<button @click="openTargetDocumentSelector()"
|
||||
<button @click="selectTextFromDocument()"
|
||||
:disabled="!linkForm.target_document_id"
|
||||
:class="linkForm.target_document_id ? 'bg-blue-500 hover:bg-blue-600 text-white' : 'bg-gray-300 text-gray-500 cursor-not-allowed'"
|
||||
class="w-full px-4 py-2 rounded-lg transition-colors flex items-center justify-center space-x-2">
|
||||
@@ -514,7 +450,7 @@
|
||||
class="px-4 py-2 text-gray-600 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors">
|
||||
취소
|
||||
</button>
|
||||
<button @click="saveDocumentLink()"
|
||||
<button @click="createDocumentLink()"
|
||||
:disabled="!linkForm?.target_document_id"
|
||||
:class="linkForm?.target_document_id ? 'bg-purple-500 hover:bg-purple-600' : 'bg-gray-300 cursor-not-allowed'"
|
||||
class="px-4 py-2 text-white rounded-lg transition-colors">
|
||||
|
||||
Reference in New Issue
Block a user