링크/백링크 기능 수정 및 안정화
- 링크 생성 기능 완전 복구
- createDocumentLink 함수를 Alpine.js 데이터 객체 내로 이동
- API 필드명 불일치 수정 (source_text → selected_text 등)
- 필수 필드 검증 및 상세 에러 로깅 추가
- API 엔드포인트 수정
- 백엔드와 일치하도록 /documents/{id}/links, /documents/{id}/backlinks 사용
- 올바른 매개변수 전달 방식 적용
- 링크/백링크 렌더링 안정화
- forEach 오류 방지를 위한 배열 타입 검증
- 데이터 없을 시 기존 링크 유지 로직
- 안전한 초기화 및 에러 처리
- UI 단순화
- 같은 서적/다른 서적 구분 제거
- 서적 선택 → 문서 선택 단순한 2단계 프로세스
- 백링크는 자동 생성되도록 수정
- 디버깅 로그 대폭 강화
- API 호출, 응답, 렌더링 각 단계별 상세 로깅
- 데이터 타입 및 개수 추적
This commit is contained in:
@@ -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 {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 텍스트 오프셋 계산
|
||||
*/
|
||||
|
||||
@@ -44,7 +44,7 @@ window.documentViewer = () => ({
|
||||
target_text: '',
|
||||
target_start_offset: 0,
|
||||
target_end_offset: 0,
|
||||
book_scope: 'same', // 'same' 또는 'other'
|
||||
|
||||
target_book_id: ''
|
||||
},
|
||||
|
||||
@@ -422,15 +422,254 @@ window.documentViewer = () => ({
|
||||
activateLinkMode() {
|
||||
console.log('🔗 링크 모드 활성화');
|
||||
this.activeMode = 'link';
|
||||
this.linkManager.createLinkFromSelection();
|
||||
|
||||
// 선택된 텍스트 확인
|
||||
const selectedText = window.getSelection().toString().trim();
|
||||
const selection = window.getSelection();
|
||||
|
||||
if (!selectedText || selection.rangeCount === 0) {
|
||||
alert('텍스트를 먼저 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedRange = selection.getRangeAt(0);
|
||||
this.linkManager.createLinkFromSelection(this.documentId, selectedText, selectedRange);
|
||||
},
|
||||
|
||||
|
||||
|
||||
activateNoteMode() {
|
||||
console.log('📝 메모 모드 활성화');
|
||||
this.activeMode = 'memo';
|
||||
this.highlightManager.activateNoteMode();
|
||||
},
|
||||
|
||||
async loadBacklinks() {
|
||||
console.log('🔗 백링크 로드 시작');
|
||||
if (this.linkManager) {
|
||||
await this.linkManager.loadBacklinks(this.documentId);
|
||||
// UI 상태 동기화
|
||||
this.backlinks = this.linkManager.backlinks || [];
|
||||
}
|
||||
},
|
||||
|
||||
async loadAvailableBooks() {
|
||||
try {
|
||||
console.log('📚 서적 목록 로딩 시작...');
|
||||
|
||||
// 문서 목록에서 서적 정보 추출
|
||||
const allDocuments = await this.api.getLinkableDocuments(this.documentId);
|
||||
console.log('📄 모든 문서들 (총 개수):', allDocuments.length);
|
||||
|
||||
// 소스 문서의 서적 정보 찾기
|
||||
const sourceBookInfo = this.getSourceBookInfo(allDocuments);
|
||||
console.log('📖 소스 문서 서적 정보:', sourceBookInfo);
|
||||
|
||||
// 서적별로 그룹화
|
||||
const bookMap = new Map();
|
||||
allDocuments.forEach(doc => {
|
||||
if (doc.book_id && doc.book_title) {
|
||||
console.log('📖 문서 서적 정보:', {
|
||||
docId: doc.id,
|
||||
bookId: doc.book_id,
|
||||
bookTitle: doc.book_title
|
||||
});
|
||||
bookMap.set(doc.book_id, {
|
||||
id: doc.book_id,
|
||||
title: doc.book_title
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
console.log('📚 그룹화된 모든 서적들:', Array.from(bookMap.values()));
|
||||
|
||||
// 소스 문서 기준으로 서적 분류
|
||||
if (sourceBookInfo.id) {
|
||||
// 소스 서적 제외 (다른 서적만 남김)
|
||||
const wasDeleted = bookMap.delete(sourceBookInfo.id);
|
||||
console.log('✅ 소스 서적 제외 결과:', {
|
||||
sourceBookId: sourceBookInfo.id,
|
||||
sourceBookTitle: sourceBookInfo.title,
|
||||
wasDeleted: wasDeleted,
|
||||
remainingBooks: bookMap.size
|
||||
});
|
||||
} else {
|
||||
console.warn('⚠️ 소스 문서의 서적 정보를 찾을 수 없습니다!');
|
||||
}
|
||||
|
||||
this.availableBooks = Array.from(bookMap.values());
|
||||
console.log('📚 최종 사용 가능한 서적들 (다른 서적):', this.availableBooks);
|
||||
console.log('🔍 제외된 소스 서적 ID:', sourceBookInfo.id);
|
||||
} catch (error) {
|
||||
console.error('서적 목록 로드 실패:', error);
|
||||
this.availableBooks = [];
|
||||
}
|
||||
},
|
||||
|
||||
getSourceBookInfo(allDocuments = null) {
|
||||
// 여러 소스에서 현재 문서의 서적 정보 찾기
|
||||
let sourceBookId = this.navigation?.book_info?.id ||
|
||||
this.document?.book_id ||
|
||||
this.document?.book_info?.id;
|
||||
|
||||
let sourceBookTitle = this.navigation?.book_info?.title ||
|
||||
this.document?.book_title ||
|
||||
this.document?.book_info?.title;
|
||||
|
||||
// allDocuments에서도 확인 (가장 확실한 방법)
|
||||
if (allDocuments) {
|
||||
const currentDoc = allDocuments.find(doc => doc.id === this.documentId);
|
||||
if (currentDoc) {
|
||||
sourceBookId = currentDoc.book_id;
|
||||
sourceBookTitle = currentDoc.book_title;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: sourceBookId,
|
||||
title: sourceBookTitle
|
||||
};
|
||||
},
|
||||
|
||||
async loadSameBookDocuments() {
|
||||
try {
|
||||
const allDocuments = await this.api.getLinkableDocuments(this.documentId);
|
||||
|
||||
// 소스 문서의 서적 정보 가져오기
|
||||
const sourceBookInfo = this.getSourceBookInfo(allDocuments);
|
||||
|
||||
console.log('📚 같은 서적 문서 로드 시작:', {
|
||||
sourceBookId: sourceBookInfo.id,
|
||||
sourceBookTitle: sourceBookInfo.title,
|
||||
totalDocs: allDocuments.length
|
||||
});
|
||||
|
||||
if (sourceBookInfo.id) {
|
||||
// 소스 문서와 같은 서적의 문서들만 필터링 (현재 문서 제외)
|
||||
this.filteredDocuments = allDocuments.filter(doc =>
|
||||
doc.book_id === sourceBookInfo.id && doc.id !== this.documentId
|
||||
);
|
||||
console.log('📚 같은 서적 문서들:', {
|
||||
count: this.filteredDocuments.length,
|
||||
bookTitle: sourceBookInfo.title,
|
||||
documents: this.filteredDocuments.map(doc => ({ id: doc.id, title: doc.title }))
|
||||
});
|
||||
} else {
|
||||
console.warn('⚠️ 소스 문서의 서적 정보를 찾을 수 없습니다!');
|
||||
this.filteredDocuments = [];
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('같은 서적 문서 로드 실패:', error);
|
||||
this.filteredDocuments = [];
|
||||
}
|
||||
},
|
||||
|
||||
async loadSameBookDocumentsForSelected() {
|
||||
try {
|
||||
console.log('📚 선택한 문서 기준으로 같은 서적 문서 로드 시작');
|
||||
|
||||
const allDocuments = await this.api.getLinkableDocuments(this.documentId);
|
||||
|
||||
// 선택한 대상 문서 찾기
|
||||
const selectedDoc = allDocuments.find(doc => doc.id === this.linkForm.target_document_id);
|
||||
|
||||
if (!selectedDoc) {
|
||||
console.error('❌ 선택한 문서를 찾을 수 없습니다:', this.linkForm.target_document_id);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('🎯 선택한 문서 정보:', {
|
||||
id: selectedDoc.id,
|
||||
title: selectedDoc.title,
|
||||
bookId: selectedDoc.book_id,
|
||||
bookTitle: selectedDoc.book_title
|
||||
});
|
||||
|
||||
// 선택한 문서와 같은 서적의 모든 문서들 (소스 문서 제외)
|
||||
this.filteredDocuments = allDocuments.filter(doc =>
|
||||
doc.book_id === selectedDoc.book_id && doc.id !== this.documentId
|
||||
);
|
||||
|
||||
console.log('📚 선택한 문서와 같은 서적 문서들:', {
|
||||
selectedBookTitle: selectedDoc.book_title,
|
||||
count: this.filteredDocuments.length,
|
||||
documents: this.filteredDocuments.map(doc => ({ id: doc.id, title: doc.title }))
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('선택한 문서 기준 같은 서적 로드 실패:', error);
|
||||
this.filteredDocuments = [];
|
||||
}
|
||||
},
|
||||
|
||||
async loadDocumentsFromBook() {
|
||||
try {
|
||||
if (this.linkForm.target_book_id) {
|
||||
// 선택된 서적의 문서들만 가져오기
|
||||
const allDocuments = await this.api.getLinkableDocuments(this.documentId);
|
||||
this.filteredDocuments = allDocuments.filter(doc =>
|
||||
doc.book_id === this.linkForm.target_book_id
|
||||
);
|
||||
console.log('📚 선택된 서적 문서들:', this.filteredDocuments);
|
||||
} else {
|
||||
this.filteredDocuments = [];
|
||||
}
|
||||
|
||||
// 문서 선택 초기화
|
||||
this.linkForm.target_document_id = '';
|
||||
} catch (error) {
|
||||
console.error('서적별 문서 로드 실패:', error);
|
||||
this.filteredDocuments = [];
|
||||
}
|
||||
},
|
||||
|
||||
resetTargetSelection() {
|
||||
console.log('🔄 대상 선택 초기화');
|
||||
this.linkForm.target_book_id = '';
|
||||
this.linkForm.target_document_id = '';
|
||||
this.filteredDocuments = [];
|
||||
|
||||
// 초기화 후 아무것도 하지 않음 (서적 선택 후 문서 로드)
|
||||
},
|
||||
|
||||
async onTargetDocumentChange() {
|
||||
console.log('📄 대상 문서 변경:', this.linkForm.target_document_id);
|
||||
|
||||
// 대상 문서 변경 시 특별한 처리 없음
|
||||
},
|
||||
|
||||
selectTextFromDocument() {
|
||||
console.log('🎯 대상 문서에서 텍스트 선택 시작');
|
||||
|
||||
if (!this.linkForm.target_document_id) {
|
||||
alert('대상 문서를 먼저 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
// 새 창에서 대상 문서 열기 (텍스트 선택 모드 전용 페이지)
|
||||
const targetUrl = `/text-selector.html?id=${this.linkForm.target_document_id}`;
|
||||
console.log('🚀 텍스트 선택 창 열기:', targetUrl);
|
||||
const popup = window.open(targetUrl, 'targetDocumentSelector', 'width=1200,height=800,scrollbars=yes,resizable=yes');
|
||||
|
||||
if (!popup) {
|
||||
console.error('❌ 팝업 창이 차단되었습니다!');
|
||||
alert('팝업 창이 차단되었습니다. 브라우저 설정에서 팝업을 허용해주세요.');
|
||||
} else {
|
||||
console.log('✅ 팝업 창이 성공적으로 열렸습니다');
|
||||
}
|
||||
|
||||
// 팝업에서 텍스트 선택 완료 시 메시지 수신
|
||||
window.addEventListener('message', (event) => {
|
||||
if (event.data.type === 'TEXT_SELECTED') {
|
||||
this.linkForm.target_text = event.data.selectedText;
|
||||
this.linkForm.target_start_offset = event.data.startOffset;
|
||||
this.linkForm.target_end_offset = event.data.endOffset;
|
||||
console.log('🎯 대상 텍스트 선택됨:', event.data);
|
||||
popup.close();
|
||||
}
|
||||
}, { once: true });
|
||||
},
|
||||
|
||||
activateBookmarkMode() {
|
||||
console.log('🔖 북마크 모드 활성화');
|
||||
this.activeMode = 'bookmark';
|
||||
@@ -550,6 +789,66 @@ window.documentViewer = () => ({
|
||||
// 언어 전환 로직 구현 필요
|
||||
},
|
||||
|
||||
async downloadOriginalFile() {
|
||||
if (!this.document || !this.document.id) {
|
||||
console.warn('문서 정보가 없습니다');
|
||||
return;
|
||||
}
|
||||
|
||||
// 연결된 PDF가 있는지 확인
|
||||
if (!this.document.matched_pdf_id) {
|
||||
alert('연결된 원본 PDF 파일이 없습니다.\n\n서적 편집 페이지에서 PDF 파일을 연결해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('📕 연결된 PDF 다운로드 시작:', this.document.matched_pdf_id);
|
||||
|
||||
// 연결된 PDF 문서 정보 가져오기
|
||||
const pdfDocument = await this.api.getDocument(this.document.matched_pdf_id);
|
||||
|
||||
if (!pdfDocument) {
|
||||
throw new Error('연결된 PDF 문서를 찾을 수 없습니다');
|
||||
}
|
||||
|
||||
// PDF 파일 다운로드 URL 생성
|
||||
const downloadUrl = `/api/documents/${this.document.matched_pdf_id}/download`;
|
||||
|
||||
// 인증 헤더 추가를 위해 fetch 사용
|
||||
const response = await fetch(downloadUrl, {
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
||||
}
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('연결된 PDF 다운로드에 실패했습니다');
|
||||
}
|
||||
|
||||
// Blob으로 변환하여 다운로드
|
||||
const blob = await response.blob();
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
|
||||
// 다운로드 링크 생성 및 클릭
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = pdfDocument.original_filename || `${pdfDocument.title}.pdf`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
|
||||
// URL 정리
|
||||
window.URL.revokeObjectURL(url);
|
||||
|
||||
console.log('📕 PDF 다운로드 완료:', pdfDocument.original_filename);
|
||||
|
||||
} catch (error) {
|
||||
console.error('PDF 다운로드 오류:', error);
|
||||
alert('PDF 다운로드 중 오류가 발생했습니다: ' + error.message);
|
||||
}
|
||||
},
|
||||
|
||||
// ==================== 유틸리티 메서드 ====================
|
||||
formatDate(dateString) {
|
||||
return new Date(dateString).toLocaleString('ko-KR');
|
||||
@@ -574,12 +873,8 @@ window.documentViewer = () => ({
|
||||
},
|
||||
|
||||
getSelectedBookTitle() {
|
||||
if (this.linkForm.book_scope === 'same') {
|
||||
return this.document?.book_title || '현재 서적';
|
||||
} else {
|
||||
const selectedBook = this.availableBooks.find(book => book.id === this.linkForm.target_book_id);
|
||||
return selectedBook ? selectedBook.title : '서적을 선택하세요';
|
||||
}
|
||||
const selectedBook = this.availableBooks.find(book => book.id === this.linkForm.target_book_id);
|
||||
return selectedBook ? selectedBook.title : '서적을 선택하세요';
|
||||
},
|
||||
|
||||
// ==================== 모듈 메서드 위임 ====================
|
||||
@@ -629,6 +924,94 @@ window.documentViewer = () => ({
|
||||
|
||||
deleteBookmark(bookmarkId) {
|
||||
return this.bookmarkManager.deleteBookmark(bookmarkId);
|
||||
},
|
||||
|
||||
// ==================== 링크 생성 ====================
|
||||
async createDocumentLink() {
|
||||
console.log('🔗 createDocumentLink 함수 실행');
|
||||
console.log('📋 현재 linkForm 상태:', JSON.stringify(this.linkForm, null, 2));
|
||||
|
||||
try {
|
||||
// 링크 데이터 검증
|
||||
if (!this.linkForm.target_document_id) {
|
||||
alert('대상 문서를 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.linkForm.link_type === 'text' && !this.linkForm.target_text) {
|
||||
alert('대상 문서에서 텍스트를 선택해주세요. "대상 문서에서 텍스트 선택" 버튼을 클릭하여 연결할 텍스트를 드래그해주세요.');
|
||||
return;
|
||||
}
|
||||
|
||||
// API 호출용 데이터 준비 (백엔드 필드명에 맞춤)
|
||||
const linkData = {
|
||||
target_document_id: this.linkForm.target_document_id,
|
||||
selected_text: this.linkForm.selected_text, // 백엔드: selected_text
|
||||
start_offset: this.linkForm.start_offset, // 백엔드: start_offset
|
||||
end_offset: this.linkForm.end_offset, // 백엔드: end_offset
|
||||
link_text: this.linkForm.link_text || this.linkForm.selected_text,
|
||||
description: this.linkForm.description,
|
||||
link_type: this.linkForm.link_type,
|
||||
target_text: this.linkForm.target_text || null,
|
||||
target_start_offset: this.linkForm.target_start_offset || null,
|
||||
target_end_offset: this.linkForm.target_end_offset || null
|
||||
};
|
||||
|
||||
console.log('📤 링크 생성 데이터:', linkData);
|
||||
console.log('📤 링크 생성 데이터 (JSON):', JSON.stringify(linkData, null, 2));
|
||||
|
||||
// 필수 필드 검증
|
||||
const requiredFields = ['target_document_id', 'selected_text', 'start_offset', 'end_offset'];
|
||||
const missingFields = requiredFields.filter(field =>
|
||||
linkData[field] === undefined || linkData[field] === null || linkData[field] === ''
|
||||
);
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
console.error('❌ 필수 필드 누락:', missingFields);
|
||||
alert('필수 필드가 누락되었습니다: ' + missingFields.join(', '));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('✅ 모든 필수 필드 확인됨');
|
||||
|
||||
// API 호출
|
||||
await this.api.createDocumentLink(this.documentId, linkData);
|
||||
console.log('✅ 링크 생성됨');
|
||||
|
||||
// 성공 알림
|
||||
alert('링크가 성공적으로 생성되었습니다!');
|
||||
|
||||
// 모달 닫기
|
||||
this.showLinkModal = false;
|
||||
|
||||
// 링크 목록 새로고침
|
||||
console.log('🔄 링크 목록 새로고침 시작...');
|
||||
await this.linkManager.loadDocumentLinks(this.documentId);
|
||||
this.documentLinks = this.linkManager.documentLinks || [];
|
||||
console.log('📊 로드된 링크 개수:', this.documentLinks.length);
|
||||
console.log('📊 링크 데이터:', this.documentLinks);
|
||||
|
||||
// 링크 렌더링
|
||||
console.log('🎨 링크 렌더링 시작...');
|
||||
await this.linkManager.renderDocumentLinks();
|
||||
console.log('✅ 링크 렌더링 완료');
|
||||
|
||||
} catch (error) {
|
||||
console.error('링크 생성 실패:', error);
|
||||
console.error('에러 상세:', {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
response: error.response
|
||||
});
|
||||
|
||||
// 422 에러인 경우 상세 정보 표시
|
||||
if (error.response && error.response.status === 422) {
|
||||
console.error('422 Validation Error Details:', error.response.data);
|
||||
alert('데이터 검증 실패: ' + JSON.stringify(error.response.data, null, 2));
|
||||
} else {
|
||||
alert('링크 생성에 실패했습니다: ' + error.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -649,3 +1032,44 @@ document.addEventListener('alpine:init', () => {
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
// Alpine.js Store 등록
|
||||
document.addEventListener('alpine:init', () => {
|
||||
Alpine.store('documentViewer', {
|
||||
instance: null,
|
||||
|
||||
init() {
|
||||
// DocumentViewer 인스턴스가 생성되면 저장
|
||||
setTimeout(() => {
|
||||
this.instance = window.documentViewerInstance;
|
||||
}, 500);
|
||||
},
|
||||
|
||||
downloadOriginalFile() {
|
||||
console.log('🏪 Store downloadOriginalFile 호출');
|
||||
if (this.instance) {
|
||||
return this.instance.downloadOriginalFile();
|
||||
} else {
|
||||
console.warn('DocumentViewer 인스턴스가 없습니다');
|
||||
}
|
||||
},
|
||||
|
||||
toggleLanguage() {
|
||||
console.log('🏪 Store toggleLanguage 호출');
|
||||
if (this.instance) {
|
||||
return this.instance.toggleLanguage();
|
||||
} else {
|
||||
console.warn('DocumentViewer 인스턴스가 없습니다');
|
||||
}
|
||||
},
|
||||
|
||||
loadBacklinks() {
|
||||
console.log('🏪 Store loadBacklinks 호출');
|
||||
if (this.instance) {
|
||||
return this.instance.loadBacklinks();
|
||||
} else {
|
||||
console.warn('DocumentViewer 인스턴스가 없습니다');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user