feat: 노트북-서적 간 양방향 링크/백링크 시스템 완성
✨ 주요 기능 - 노트 ↔ 서적 문서 간 양방향 링크 생성 및 이동 - 링크 대상 타입 선택 UI (서적 문서/노트북 노트) - 통합 백링크 시스템 (일반 문서에서 노트 백링크도 표시) - 링크 목록 UI 개선 (상세 정보 표시, 타입 구분) 🔧 백엔드 개선 - NoteLink 모델 및 API 추가 (/note-documents/{id}/links, /note-documents/{id}/backlinks) - 일반 문서 백링크 API에서 노트 링크도 함께 조회 - target_content_type, source_content_type 필드 추가 - 노트 문서 콘텐츠 API 추가 (/note-documents/{id}/content) 🎨 프론트엔드 개선 - text-selector.html에서 노트 문서 지원 - 링크 이동 시 contentType에 따른 올바른 URL 생성 - URL 파라미터 파싱 수정 (contentType 지원) - 링크 타입 자동 추론 로직 - 링크 목록 UI 대폭 개선 (출발점/도착점 텍스트, 타입 배지 등) 🐛 버그 수정 - 서적 목록 로드 실패 문제 해결 - 노트에서 링크 생성 시 대상 문서 열기 문제 해결 - 더미 문서로 이동하는 문제 해결 - 캐시 관련 문제 해결
This commit is contained in:
@@ -311,29 +311,45 @@
|
||||
<template x-for="link in documentLinks" :key="link.id">
|
||||
<div class="border rounded-lg p-4 mb-3 hover:bg-purple-50 cursor-pointer transition-colors"
|
||||
@click="navigateToLink(link)">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1">
|
||||
<div class="font-medium text-purple-700 mb-1" x-text="link.target_document_title"></div>
|
||||
|
||||
<!-- 선택된 텍스트 또는 문서 전체 링크 -->
|
||||
<div x-show="link.selected_text" class="mb-2">
|
||||
<div class="text-sm text-gray-600 bg-gray-100 px-3 py-2 rounded border-l-4 border-purple-500" x-text="link.selected_text"></div>
|
||||
<!-- 대상 문서 제목 -->
|
||||
<div class="font-medium text-purple-700 mb-2 flex items-center">
|
||||
<span x-text="link.target_document_title || link.target_note_title"></span>
|
||||
<span x-show="link.target_content_type === 'note'" class="ml-2 text-xs bg-blue-100 text-blue-700 px-2 py-1 rounded">노트</span>
|
||||
<span x-show="link.target_content_type === 'document'" class="ml-2 text-xs bg-green-100 text-green-700 px-2 py-1 rounded">문서</span>
|
||||
</div>
|
||||
<div x-show="!link.selected_text" class="mb-2">
|
||||
<div class="text-sm text-gray-600 italic">📄 문서 전체 링크</div>
|
||||
|
||||
<!-- 현재 문서에서 선택한 텍스트 (출발점) -->
|
||||
<div x-show="link.selected_text" class="mb-3">
|
||||
<div class="text-xs text-gray-500 mb-1">📍 현재 문서에서 선택한 텍스트:</div>
|
||||
<div class="text-sm text-gray-700 bg-purple-50 px-3 py-2 rounded border-l-4 border-purple-500" x-text="link.selected_text"></div>
|
||||
</div>
|
||||
|
||||
<!-- 대상 문서의 텍스트 (도착점) -->
|
||||
<div x-show="link.target_text" class="mb-3">
|
||||
<div class="text-xs text-gray-500 mb-1">🎯 대상 문서의 텍스트:</div>
|
||||
<div class="text-sm text-gray-700 bg-blue-50 px-3 py-2 rounded border-l-4 border-blue-500" x-text="link.target_text"></div>
|
||||
</div>
|
||||
|
||||
<!-- 문서 전체 링크인 경우 -->
|
||||
<div x-show="!link.selected_text && !link.target_text" class="mb-3">
|
||||
<div class="text-sm text-gray-600 italic bg-gray-50 px-3 py-2 rounded">📄 문서 전체 링크</div>
|
||||
</div>
|
||||
|
||||
<!-- 설명 -->
|
||||
<div x-show="link.description" class="text-sm text-gray-600 mb-2" x-text="link.description"></div>
|
||||
<div x-show="link.description" class="mb-3">
|
||||
<div class="text-xs text-gray-500 mb-1">💬 설명:</div>
|
||||
<div class="text-sm text-gray-600 bg-yellow-50 px-3 py-2 rounded" x-text="link.description"></div>
|
||||
</div>
|
||||
|
||||
<!-- 링크 타입과 날짜 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-xs text-gray-500"
|
||||
x-text="link.link_type === 'text_fragment' ? '텍스트 조각 링크' : '문서 링크'"></span>
|
||||
<span class="text-xs text-gray-500" x-text="formatDate(link.created_at)"></span>
|
||||
<div class="flex items-center justify-between text-xs text-gray-500">
|
||||
<span x-text="link.link_type === 'text_fragment' ? '🔗 텍스트 조각 링크' : '📄 문서 링크'"></span>
|
||||
<span x-text="formatDate(link.created_at)"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-3">
|
||||
<div class="ml-3 flex-shrink-0">
|
||||
<svg class="w-5 h-5 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14"></path>
|
||||
</svg>
|
||||
@@ -376,15 +392,39 @@
|
||||
<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>
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3">링크 대상 타입</label>
|
||||
<div class="flex space-x-4">
|
||||
<label class="flex items-center">
|
||||
<input type="radio"
|
||||
x-model="linkForm.target_type"
|
||||
value="document"
|
||||
@change="onTargetTypeChange()"
|
||||
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="linkForm.target_type"
|
||||
value="note"
|
||||
@change="onTargetTypeChange()"
|
||||
class="mr-2 text-blue-600">
|
||||
<span class="text-sm text-gray-700">📝 노트북 노트</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 서적/노트북 선택 -->
|
||||
<div class="mb-6" x-show="linkForm.target_type">
|
||||
<label class="block text-sm font-semibold text-gray-700 mb-3"
|
||||
x-text="linkForm.target_type === 'note' ? '노트북 선택' : '서적 선택'"></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>
|
||||
<option value="" x-text="linkForm.target_type === 'note' ? '노트북을 선택하세요' : '서적을 선택하세요'"></option>
|
||||
<template x-for="book in availableBooks" :key="book.id">
|
||||
<option :value="book.id" x-text="book.title"></option>
|
||||
</template>
|
||||
@@ -404,8 +444,10 @@
|
||||
: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.target_book_id">먼저 서적을 선택하세요</span>
|
||||
<span x-show="linkForm.target_book_id">문서를 선택하세요</span>
|
||||
<span x-show="!linkForm.target_book_id"
|
||||
x-text="linkForm.target_type === 'note' ? '먼저 노트북을 선택하세요' : '먼저 서적을 선택하세요'"></span>
|
||||
<span x-show="linkForm.target_book_id"
|
||||
x-text="linkForm.target_type === 'note' ? '노트를 선택하세요' : '문서를 선택하세요'"></span>
|
||||
</option>
|
||||
<template x-for="doc in filteredDocuments" :key="doc.id">
|
||||
<option :value="doc.id" x-text="doc.title"></option>
|
||||
@@ -501,8 +543,8 @@
|
||||
<div class="bg-gray-50 rounded-lg p-4 hover:bg-gray-100 transition-colors cursor-pointer"
|
||||
@click="scrollToHighlight(note.highlight.id)">
|
||||
<!-- 선택된 텍스트 -->
|
||||
<div class="bg-blue-50 rounded-md p-2 mb-3">
|
||||
<p class="text-sm text-blue-800 font-medium" x-text="note.highlight.selected_text"></p>
|
||||
<div class="bg-blue-50 rounded-md p-2 mb-3" x-show="note.highlight?.selected_text">
|
||||
<p class="text-sm text-blue-800 font-medium" x-text="note.highlight?.selected_text || ''"></p>
|
||||
</div>
|
||||
|
||||
<!-- 메모 내용 -->
|
||||
|
||||
Reference in New Issue
Block a user