링크/백링크 기능 수정 및 안정화

- 링크 생성 기능 완전 복구
  - createDocumentLink 함수를 Alpine.js 데이터 객체 내로 이동
  - API 필드명 불일치 수정 (source_text → selected_text 등)
  - 필수 필드 검증 및 상세 에러 로깅 추가

- API 엔드포인트 수정
  - 백엔드와 일치하도록 /documents/{id}/links, /documents/{id}/backlinks 사용
  - 올바른 매개변수 전달 방식 적용

- 링크/백링크 렌더링 안정화
  - forEach 오류 방지를 위한 배열 타입 검증
  - 데이터 없을 시 기존 링크 유지 로직
  - 안전한 초기화 및 에러 처리

- UI 단순화
  - 같은 서적/다른 서적 구분 제거
  - 서적 선택 → 문서 선택 단순한 2단계 프로세스
  - 백링크는 자동 생성되도록 수정

- 디버깅 로그 대폭 강화
  - API 호출, 응답, 렌더링 각 단계별 상세 로깅
  - 데이터 타입 및 개수 추적
This commit is contained in:
Hyungi Ahn
2025-08-28 08:48:52 +09:00
parent 5d4465b15c
commit 844587c86f
5 changed files with 561 additions and 131 deletions

View File

@@ -10,6 +10,9 @@ class LinkManager {
this.cachedApi = window.cachedApi || api;
this.documentLinks = [];
this.backlinks = [];
// 안전한 초기화 확인
console.log('🔧 LinkManager 초기화 - backlinks 타입:', typeof this.backlinks, Array.isArray(this.backlinks));
this.selectedText = '';
this.selectedRange = null;
this.availableBooks = [];
@@ -23,10 +26,15 @@ class LinkManager {
*/
async loadDocumentLinks(documentId) {
try {
this.documentLinks = await this.cachedApi.get('/document-links', { document_id: documentId }, { category: 'links' }).catch(() => []);
return this.documentLinks || [];
console.log('📡 링크 API 호출:', `/documents/${documentId}/links`);
const response = await this.cachedApi.get(`/documents/${documentId}/links`, {}, { category: 'links' }).catch(() => []);
this.documentLinks = Array.isArray(response) ? response : [];
console.log('📡 API 응답 링크 개수:', this.documentLinks.length);
console.log('📡 API 응답 타입:', typeof response, response);
return this.documentLinks;
} catch (error) {
console.error('문서 링크 로드 실패:', error);
this.documentLinks = [];
return [];
}
}
@@ -36,10 +44,15 @@ class LinkManager {
*/
async loadBacklinks(documentId) {
try {
this.backlinks = await this.cachedApi.get('/document-links/backlinks', { target_document_id: documentId }, { category: 'links' }).catch(() => []);
return this.backlinks || [];
console.log('📡 백링크 API 호출:', `/documents/${documentId}/backlinks`);
const response = await this.cachedApi.get(`/documents/${documentId}/backlinks`, {}, { category: 'links' }).catch(() => []);
this.backlinks = Array.isArray(response) ? response : [];
console.log('📡 API 응답 백링크 개수:', this.backlinks.length);
console.log('📡 API 응답 타입:', typeof response, response);
return this.backlinks;
} catch (error) {
console.error('백링크 로드 실패:', error);
this.backlinks = [];
return [];
}
}
@@ -51,7 +64,20 @@ class LinkManager {
const documentContent = document.getElementById('document-content');
if (!documentContent) return;
// 안전한 링크 초기화
if (!Array.isArray(this.documentLinks)) {
console.warn('⚠️ this.documentLinks가 배열이 아닙니다. 빈 배열로 초기화합니다.');
console.log('🔍 기존 this.documentLinks:', typeof this.documentLinks, this.documentLinks);
this.documentLinks = [];
}
console.log('🔗 링크 렌더링 시작 - 총', this.documentLinks.length, '개');
// 링크 데이터가 없으면 렌더링하지 않음 (기존 링크 유지)
if (this.documentLinks.length === 0) {
console.log('📝 링크 데이터가 없어서 기존 링크를 유지합니다.');
return;
}
// 기존 링크 제거
const existingLinks = documentContent.querySelectorAll('.document-link');
@@ -62,9 +88,13 @@ class LinkManager {
});
// 각 링크 렌더링
this.documentLinks.forEach(link => {
this.renderSingleLink(link);
});
if (Array.isArray(this.documentLinks)) {
this.documentLinks.forEach(link => {
this.renderSingleLink(link);
});
} else {
console.warn('⚠️ this.documentLinks가 배열이 아닙니다:', typeof this.documentLinks, this.documentLinks);
}
console.log('✅ 링크 렌더링 완료');
}
@@ -166,26 +196,43 @@ class LinkManager {
const documentContent = document.getElementById('document-content');
if (!documentContent) return;
// 안전한 백링크 초기화
if (!Array.isArray(this.backlinks)) {
console.warn('⚠️ this.backlinks가 배열이 아닙니다. 빈 배열로 초기화합니다.');
console.log('🔍 기존 this.backlinks:', typeof this.backlinks, this.backlinks);
this.backlinks = [];
}
console.log('🔗 백링크 렌더링 시작 - 총', this.backlinks.length, '개');
// 백링크 데이터가 없으면 렌더링하지 않음 (기존 백링크 유지)
if (this.backlinks.length === 0) {
console.log('📝 백링크 데이터가 없어서 기존 백링크를 유지합니다.');
return;
}
// 기존 백링크는 제거하지 않고 중복 체크만 함
const existingBacklinks = documentContent.querySelectorAll('.backlink-highlight');
console.log(`🔍 기존 백링크 ${existingBacklinks.length}개 발견 (유지)`);
// 각 백링크 렌더링 (중복되지 않는 것만)
this.backlinks.forEach(backlink => {
// 이미 렌더링된 백링크인지 확인
const existingBacklink = Array.from(existingBacklinks).find(el =>
el.dataset.backlinkId === backlink.id.toString()
);
if (!existingBacklink) {
console.log(`🆕 새로운 백링크 렌더링: ${backlink.id}`);
this.renderSingleBacklink(backlink);
} else {
console.log(`✅ 백링크 이미 존재: ${backlink.id}`);
}
});
if (Array.isArray(this.backlinks)) {
this.backlinks.forEach(backlink => {
// 이미 렌더링된 백링크인지 확인
const existingBacklink = Array.from(existingBacklinks).find(el =>
el.dataset.backlinkId === backlink.id.toString()
);
if (!existingBacklink) {
console.log(`🆕 새로운 백링크 렌더링: ${backlink.id}`);
this.renderSingleBacklink(backlink);
} else {
console.log(`✅ 백링크 이미 존재: ${backlink.id}`);
}
});
} else {
console.warn('⚠️ this.backlinks가 배열이 아닙니다:', typeof this.backlinks, this.backlinks);
}
console.log('✅ 백링크 렌더링 완료');
}
@@ -534,8 +581,23 @@ class LinkManager {
/**
* 선택된 텍스트로 링크 생성
*/
async createLinkFromSelection(documentId, selectedText, selectedRange) {
if (!selectedText || !selectedRange) return;
async createLinkFromSelection(documentId = null, selectedText = null, selectedRange = null) {
// 매개변수가 없으면 현재 선택된 텍스트 사용
if (!selectedText || !selectedRange) {
selectedText = window.getSelection().toString().trim();
const selection = window.getSelection();
if (!selectedText || selection.rangeCount === 0) {
alert('텍스트를 먼저 선택해주세요.');
return;
}
selectedRange = selection.getRangeAt(0);
}
if (!documentId && window.documentViewerInstance) {
documentId = window.documentViewerInstance.documentId;
}
try {
console.log('🔗 링크 생성 시작:', selectedText);
@@ -547,6 +609,12 @@ class LinkManager {
window.documentViewerInstance.showLinkModal = true;
window.documentViewerInstance.linkForm.selected_text = selectedText;
// 서적 목록 로드
await window.documentViewerInstance.loadAvailableBooks();
// 기본적으로 같은 서적 문서들 로드
await window.documentViewerInstance.loadSameBookDocuments();
// 텍스트 오프셋 계산
const documentContent = document.getElementById('document-content');
const fullText = documentContent.textContent;
@@ -563,6 +631,8 @@ class LinkManager {
}
}
/**
* 텍스트 오프셋 계산
*/