- PDF 다운로드 기능 복원 (직접 다운로드 + 연결된 PDF 지원) - 언어 전환 기능 수정 (문서 내장 기능 활용) - 헤더 네비게이션 버튼 구현 (뒤로가기, 목차, 이전/다음 문서) - PDF 매칭 UI 추가 (book-documents.html) - 링크/백링크 렌더링 안정화 - 서적 URL 파라미터 수정 (book_id 지원) 주요 수정사항: - viewer-core.js: PDF 다운로드 로직 개선, 언어 전환 단순화 - book-documents.js/html: PDF 매칭 기능 및 URL 파라미터 수정 - components/header.html: 언어 전환 및 PDF 버튼 추가 - 모든 기능 테스트 완료 및 정상 작동 확인
1270 lines
48 KiB
JavaScript
1270 lines
48 KiB
JavaScript
/**
|
|
* ViewerCore - 문서 뷰어 Alpine.js 컴포넌트
|
|
* 모든 모듈을 통합하고 Alpine.js 컴포넌트를 관리합니다.
|
|
*/
|
|
window.documentViewer = () => ({
|
|
// ==================== 기본 상태 ====================
|
|
loading: true,
|
|
error: null,
|
|
document: null,
|
|
documentId: null,
|
|
contentType: 'document', // 'document' 또는 'note'
|
|
navigation: null,
|
|
|
|
// ==================== 데이터 상태 ====================
|
|
highlights: [],
|
|
notes: [],
|
|
bookmarks: [],
|
|
documentLinks: [],
|
|
linkableDocuments: [],
|
|
backlinks: [],
|
|
|
|
// ==================== 선택 상태 ====================
|
|
selectedHighlightColor: '#FFFF00',
|
|
selectedText: '',
|
|
selectedRange: null,
|
|
|
|
// ==================== 폼 데이터 ====================
|
|
noteForm: {
|
|
content: '',
|
|
tags: ''
|
|
},
|
|
bookmarkForm: {
|
|
title: '',
|
|
description: ''
|
|
},
|
|
linkForm: {
|
|
target_document_id: '',
|
|
selected_text: '',
|
|
start_offset: 0,
|
|
end_offset: 0,
|
|
link_text: '',
|
|
description: '',
|
|
link_type: 'text_fragment', // 무조건 텍스트 선택만 지원
|
|
target_text: '',
|
|
target_start_offset: 0,
|
|
target_end_offset: 0,
|
|
|
|
target_book_id: ''
|
|
},
|
|
|
|
// ==================== 언어 및 기타 ====================
|
|
isKorean: false,
|
|
|
|
// ==================== UI 상태 (Alpine.js 바인딩용) ====================
|
|
searchQuery: '',
|
|
activeFeatureMenu: null,
|
|
showLinksModal: false,
|
|
showLinkModal: false,
|
|
showNotesModal: false,
|
|
showBookmarksModal: false,
|
|
showBacklinksModal: false,
|
|
showNoteInputModal: false,
|
|
availableBooks: [],
|
|
filteredDocuments: [],
|
|
|
|
// ==================== 모듈 인스턴스 ====================
|
|
documentLoader: null,
|
|
highlightManager: null,
|
|
bookmarkManager: null,
|
|
linkManager: null,
|
|
uiManager: null,
|
|
|
|
// ==================== 초기화 플래그 ====================
|
|
_initialized: false,
|
|
|
|
// ==================== 초기화 ====================
|
|
async init() {
|
|
// 중복 초기화 방지
|
|
if (this._initialized) {
|
|
console.log('⚠️ 이미 초기화됨, 중복 실행 방지');
|
|
return;
|
|
}
|
|
this._initialized = true;
|
|
|
|
console.log('🚀 DocumentViewer 초기화 시작');
|
|
|
|
// 전역 인스턴스 설정 (말풍선에서 함수 호출용)
|
|
window.documentViewerInstance = this;
|
|
|
|
try {
|
|
// 모듈 초기화
|
|
await this.initializeModules();
|
|
|
|
// URL 파라미터 처리
|
|
this.parseUrlParameters();
|
|
|
|
// 문서 로드
|
|
await this.loadDocument();
|
|
|
|
console.log('✅ DocumentViewer 초기화 완료');
|
|
} catch (error) {
|
|
console.error('❌ DocumentViewer 초기화 실패:', error);
|
|
this.error = error.message;
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
// ==================== 모듈 초기화 (지연 로딩 + 폴백) ====================
|
|
async initializeModules() {
|
|
console.log('🔧 모듈 초기화 시작 (지연 로딩)');
|
|
|
|
// API 및 캐시 초기화
|
|
this.api = new DocumentServerAPI();
|
|
|
|
// 토큰 설정 (인증 확인)
|
|
const token = localStorage.getItem('access_token');
|
|
if (token) {
|
|
this.api.setToken(token);
|
|
console.log('🔐 API 토큰 설정 완료');
|
|
} else {
|
|
console.error('❌ 인증 토큰이 없습니다!');
|
|
throw new Error('인증이 필요합니다');
|
|
}
|
|
|
|
this.cache = new CacheManager();
|
|
this.cachedApi = new CachedAPI(this.api, this.cache);
|
|
|
|
// 직접 모듈 인스턴스 생성 (모든 모듈이 HTML에서 로드됨)
|
|
if (window.DocumentLoader && window.UIManager && window.HighlightManager &&
|
|
window.LinkManager && window.BookmarkManager) {
|
|
|
|
this.documentLoader = new window.DocumentLoader(this.cachedApi);
|
|
this.uiManager = new window.UIManager();
|
|
this.highlightManager = new window.HighlightManager(this.cachedApi);
|
|
this.linkManager = new window.LinkManager(this.cachedApi);
|
|
this.bookmarkManager = new window.BookmarkManager(this.cachedApi);
|
|
|
|
console.log('✅ 모든 모듈 직접 로드 성공');
|
|
} else {
|
|
console.error('❌ 필수 모듈이 로드되지 않음');
|
|
console.log('사용 가능한 모듈:', {
|
|
DocumentLoader: !!window.DocumentLoader,
|
|
UIManager: !!window.UIManager,
|
|
HighlightManager: !!window.HighlightManager,
|
|
LinkManager: !!window.LinkManager,
|
|
BookmarkManager: !!window.BookmarkManager
|
|
});
|
|
throw new Error('필수 모듈을 로드할 수 없습니다.');
|
|
}
|
|
|
|
// UI 상태를 UIManager와 동기화 (모달은 초기화 시 닫힌 상태로)
|
|
this.syncUIState();
|
|
|
|
// 초기화 시 모든 모달을 명시적으로 닫기
|
|
this.closeAllModals();
|
|
|
|
// 나머지 모듈들은 백그라운드에서 프리로딩 (지연 로딩 가능한 경우만)
|
|
if (window.moduleLoader) {
|
|
window.moduleLoader.preloadModules(['HighlightManager', 'BookmarkManager', 'LinkManager']);
|
|
}
|
|
|
|
console.log('✅ 모듈 초기화 완료');
|
|
},
|
|
|
|
// ==================== UI 상태 동기화 ====================
|
|
syncUIState() {
|
|
// UIManager의 상태를 Alpine.js 컴포넌트와 동기화 (getter/setter 방식)
|
|
|
|
// 패널 상태 동기화
|
|
Object.defineProperty(this, 'showNotesPanel', {
|
|
get: () => this.uiManager.showNotesPanel,
|
|
set: (value) => { this.uiManager.showNotesPanel = value; }
|
|
});
|
|
|
|
Object.defineProperty(this, 'showBookmarksPanel', {
|
|
get: () => this.uiManager.showBookmarksPanel,
|
|
set: (value) => { this.uiManager.showBookmarksPanel = value; }
|
|
});
|
|
|
|
Object.defineProperty(this, 'showBacklinks', {
|
|
get: () => this.uiManager.showBacklinks,
|
|
set: (value) => { this.uiManager.showBacklinks = value; }
|
|
});
|
|
|
|
Object.defineProperty(this, 'activePanel', {
|
|
get: () => this.uiManager.activePanel,
|
|
set: (value) => { this.uiManager.activePanel = value; }
|
|
});
|
|
|
|
// 모달 상태 동기화 (UIManager와 실시간 연동)
|
|
this.updateModalStates();
|
|
|
|
// 검색 상태 동기화
|
|
Object.defineProperty(this, 'noteSearchQuery', {
|
|
get: () => this.uiManager.noteSearchQuery,
|
|
set: (value) => { this.uiManager.updateNoteSearchQuery(value); }
|
|
});
|
|
|
|
Object.defineProperty(this, 'filteredNotes', {
|
|
get: () => this.uiManager.filteredNotes,
|
|
set: (value) => { this.uiManager.filteredNotes = value; }
|
|
});
|
|
|
|
// 모드 및 핸들러 상태
|
|
this.activeMode = null;
|
|
this.textSelectionHandler = null;
|
|
this.editingNote = null;
|
|
this.editingBookmark = null;
|
|
this.editingLink = null;
|
|
this.noteLoading = false;
|
|
this.bookmarkLoading = false;
|
|
this.linkLoading = false;
|
|
},
|
|
|
|
// ==================== 모달 상태 업데이트 ====================
|
|
updateModalStates() {
|
|
// UIManager의 모달 상태를 ViewerCore의 속성에 반영
|
|
if (this.uiManager) {
|
|
this.showLinksModal = this.uiManager.showLinksModal;
|
|
this.showLinkModal = this.uiManager.showLinkModal;
|
|
this.showNotesModal = this.uiManager.showNotesModal;
|
|
this.showBookmarksModal = this.uiManager.showBookmarksModal;
|
|
this.showBacklinksModal = this.uiManager.showBacklinksModal;
|
|
this.activeFeatureMenu = this.uiManager.activeFeatureMenu;
|
|
this.searchQuery = this.uiManager.searchQuery;
|
|
}
|
|
},
|
|
|
|
// ==================== 모든 모달 닫기 ====================
|
|
closeAllModals() {
|
|
console.log('🔒 초기화 시 모든 모달 닫기');
|
|
this.showLinksModal = false;
|
|
this.showLinkModal = false;
|
|
this.showNotesModal = false;
|
|
this.showBookmarksModal = false;
|
|
this.showBacklinksModal = false;
|
|
this.showNoteInputModal = false;
|
|
|
|
// UIManager에도 반영
|
|
if (this.uiManager) {
|
|
this.uiManager.closeAllModals();
|
|
}
|
|
},
|
|
|
|
// ==================== URL 파라미터 처리 ====================
|
|
parseUrlParameters() {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
this.documentId = urlParams.get('id');
|
|
this.contentType = urlParams.get('type') || 'document';
|
|
|
|
console.log('🔍 URL 파싱 결과:', {
|
|
documentId: this.documentId,
|
|
contentType: this.contentType
|
|
});
|
|
|
|
if (!this.documentId) {
|
|
throw new Error('문서 ID가 필요합니다.');
|
|
}
|
|
},
|
|
|
|
// ==================== 문서 로드 ====================
|
|
async loadDocument() {
|
|
console.log('📄 문서 로드 시작');
|
|
this.loading = true;
|
|
|
|
try {
|
|
// 문서 데이터 로드
|
|
if (this.contentType === 'note') {
|
|
this.document = await this.documentLoader.loadNote(this.documentId);
|
|
this.navigation = null; // 노트는 네비게이션 없음
|
|
} else {
|
|
this.document = await this.documentLoader.loadDocument(this.documentId);
|
|
// 네비게이션 별도 로드
|
|
this.navigation = await this.documentLoader.loadNavigation(this.documentId);
|
|
}
|
|
|
|
// 관련 데이터 병렬 로드
|
|
await this.loadDocumentData();
|
|
|
|
// 데이터를 모듈에 전달
|
|
this.distributeDataToModules();
|
|
|
|
// 렌더링
|
|
await this.renderAllFeatures();
|
|
|
|
// URL 하이라이트 처리
|
|
await this.handleUrlHighlight();
|
|
|
|
this.loading = false;
|
|
console.log('✅ 문서 로드 완료');
|
|
|
|
} catch (error) {
|
|
console.error('❌ 문서 로드 실패:', error);
|
|
this.error = error.message;
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
// ==================== 문서 데이터 로드 (지연 로딩) ====================
|
|
async loadDocumentData() {
|
|
console.log('📊 문서 데이터 로드 시작');
|
|
|
|
const [highlights, notes, bookmarks, documentLinks, backlinks] = await Promise.all([
|
|
this.highlightManager.loadHighlights(this.documentId, this.contentType),
|
|
this.highlightManager.loadNotes(this.documentId, this.contentType),
|
|
this.bookmarkManager.loadBookmarks(this.documentId),
|
|
this.linkManager.loadDocumentLinks(this.documentId),
|
|
this.linkManager.loadBacklinks(this.documentId)
|
|
]);
|
|
|
|
// 데이터 저장 및 모듈 동기화
|
|
this.highlights = highlights;
|
|
this.notes = notes;
|
|
this.bookmarks = bookmarks;
|
|
this.documentLinks = documentLinks;
|
|
this.backlinks = backlinks;
|
|
|
|
// 모듈에 데이터 동기화 (중요!)
|
|
this.linkManager.documentLinks = documentLinks;
|
|
this.linkManager.backlinks = backlinks;
|
|
|
|
console.log('📊 로드된 데이터:', {
|
|
highlights: highlights.length,
|
|
notes: notes.length,
|
|
bookmarks: bookmarks.length,
|
|
documentLinks: documentLinks.length,
|
|
backlinks: backlinks.length
|
|
});
|
|
|
|
console.log('🔄 모듈 데이터 동기화 완료:', {
|
|
'linkManager.documentLinks': this.linkManager.documentLinks?.length || 0,
|
|
'linkManager.backlinks': this.linkManager.backlinks?.length || 0
|
|
});
|
|
},
|
|
|
|
// ==================== 모듈 지연 로딩 보장 (폴백 포함) ====================
|
|
async ensureModulesLoaded(moduleNames) {
|
|
const missingModules = [];
|
|
|
|
for (const moduleName of moduleNames) {
|
|
const propertyName = this.getModulePropertyName(moduleName);
|
|
if (!this[propertyName]) {
|
|
missingModules.push(moduleName);
|
|
}
|
|
}
|
|
|
|
if (missingModules.length > 0) {
|
|
console.log(`🔄 필요한 모듈 지연 로딩: ${missingModules.join(', ')}`);
|
|
|
|
// 각 모듈을 개별적으로 로드
|
|
for (const moduleName of missingModules) {
|
|
const propertyName = this.getModulePropertyName(moduleName);
|
|
|
|
try {
|
|
// 지연 로딩 시도
|
|
if (window.moduleLoader) {
|
|
const ModuleClass = await window.moduleLoader.loadModule(moduleName);
|
|
|
|
if (moduleName === 'UIManager') {
|
|
this[propertyName] = new ModuleClass();
|
|
} else {
|
|
this[propertyName] = new ModuleClass(this.cachedApi);
|
|
}
|
|
|
|
console.log(`✅ 지연 로딩 성공: ${moduleName}`);
|
|
} else {
|
|
throw new Error('ModuleLoader 없음');
|
|
}
|
|
} catch (error) {
|
|
console.warn(`⚠️ 지연 로딩 실패, 폴백 시도: ${moduleName}`, error);
|
|
|
|
// 폴백: 전역 클래스 직접 사용
|
|
if (window[moduleName]) {
|
|
if (moduleName === 'UIManager') {
|
|
this[propertyName] = new window[moduleName]();
|
|
} else {
|
|
this[propertyName] = new window[moduleName](this.cachedApi);
|
|
}
|
|
|
|
console.log(`✅ 폴백 성공: ${moduleName}`);
|
|
} else {
|
|
console.error(`❌ 폴백도 실패: ${moduleName} - 전역 클래스 없음`);
|
|
throw new Error(`모듈을 로드할 수 없습니다: ${moduleName}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
// ==================== 모듈명 → 속성명 변환 ====================
|
|
getModulePropertyName(moduleName) {
|
|
const nameMap = {
|
|
'DocumentLoader': 'documentLoader',
|
|
'HighlightManager': 'highlightManager',
|
|
'BookmarkManager': 'bookmarkManager',
|
|
'LinkManager': 'linkManager',
|
|
'UIManager': 'uiManager'
|
|
};
|
|
return nameMap[moduleName];
|
|
},
|
|
|
|
// ==================== 모듈에 데이터 분배 ====================
|
|
distributeDataToModules() {
|
|
// HighlightManager에 데이터 전달
|
|
this.highlightManager.highlights = this.highlights;
|
|
this.highlightManager.notes = this.notes;
|
|
|
|
// BookmarkManager에 데이터 전달
|
|
this.bookmarkManager.bookmarks = this.bookmarks;
|
|
|
|
// LinkManager에 데이터 전달
|
|
this.linkManager.documentLinks = this.documentLinks;
|
|
this.linkManager.backlinks = this.backlinks;
|
|
},
|
|
|
|
// ==================== 모든 기능 렌더링 ====================
|
|
async renderAllFeatures() {
|
|
console.log('🎨 모든 기능 렌더링 시작');
|
|
|
|
// 하이라이트 렌더링
|
|
this.highlightManager.renderHighlights();
|
|
|
|
// 백링크 먼저 렌더링 (링크보다 먼저)
|
|
this.linkManager.renderBacklinks();
|
|
|
|
// 문서 링크 렌더링 (백링크 후에 렌더링)
|
|
this.linkManager.renderDocumentLinks();
|
|
|
|
console.log('✅ 모든 기능 렌더링 완료');
|
|
},
|
|
|
|
// ==================== URL 하이라이트 처리 ====================
|
|
async handleUrlHighlight() {
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const highlightText = urlParams.get('highlight');
|
|
const startOffset = parseInt(urlParams.get('start_offset'));
|
|
const endOffset = parseInt(urlParams.get('end_offset'));
|
|
|
|
if (highlightText || (startOffset && endOffset)) {
|
|
console.log('🎯 URL에서 하이라이트 요청:', { highlightText, startOffset, endOffset });
|
|
await this.documentLoader.highlightAndScrollToText({
|
|
text: highlightText,
|
|
start_offset: startOffset,
|
|
end_offset: endOffset
|
|
});
|
|
}
|
|
},
|
|
|
|
// ==================== 기능 모드 활성화 ====================
|
|
activateLinkMode() {
|
|
console.log('🔗 링크 모드 활성화');
|
|
this.activeMode = 'link';
|
|
|
|
// 선택된 텍스트 확인
|
|
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()));
|
|
|
|
// 모든 서적 표시 (소스 서적 포함)
|
|
this.availableBooks = Array.from(bookMap.values());
|
|
console.log('📚 최종 사용 가능한 서적들 (모든 서적):', this.availableBooks);
|
|
console.log('📖 소스 서적 정보 (포함됨):', sourceBookInfo);
|
|
} 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';
|
|
this.bookmarkManager.activateBookmarkMode();
|
|
},
|
|
|
|
// ==================== 하이라이트 기능 위임 ====================
|
|
createHighlightWithColor(color) {
|
|
console.log('🎨 하이라이트 생성 요청:', color);
|
|
// ViewerCore의 selectedHighlightColor도 동기화
|
|
this.selectedHighlightColor = color;
|
|
console.log('🎨 ViewerCore 색상 동기화:', this.selectedHighlightColor);
|
|
return this.highlightManager.createHighlightWithColor(color);
|
|
},
|
|
|
|
// ==================== 메모 입력 모달 관련 ====================
|
|
openNoteInputModal() {
|
|
console.log('📝 메모 입력 모달 열기');
|
|
this.showNoteInputModal = true;
|
|
// 폼 초기화
|
|
this.noteForm.content = '';
|
|
this.noteForm.tags = '';
|
|
// 포커스를 textarea로 이동 (다음 틱에서)
|
|
this.$nextTick(() => {
|
|
const textarea = document.querySelector('textarea[x-model="noteForm.content"]');
|
|
if (textarea) textarea.focus();
|
|
});
|
|
},
|
|
|
|
closeNoteInputModal() {
|
|
console.log('📝 메모 입력 모달 닫기');
|
|
this.showNoteInputModal = false;
|
|
this.noteForm.content = '';
|
|
this.noteForm.tags = '';
|
|
// 선택된 텍스트 정리
|
|
this.selectedText = '';
|
|
this.selectedRange = null;
|
|
},
|
|
|
|
async createNoteForHighlight() {
|
|
console.log('📝 하이라이트에 메모 생성');
|
|
if (!this.noteForm.content.trim()) {
|
|
alert('메모 내용을 입력해주세요.');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 현재 생성된 하이라이트 정보가 필요함
|
|
if (this.highlightManager.lastCreatedHighlight) {
|
|
await this.highlightManager.createNoteForHighlight(
|
|
this.highlightManager.lastCreatedHighlight,
|
|
this.noteForm.content.trim(),
|
|
this.noteForm.tags.trim()
|
|
);
|
|
this.closeNoteInputModal();
|
|
} else {
|
|
alert('하이라이트 정보를 찾을 수 없습니다.');
|
|
}
|
|
} catch (error) {
|
|
console.error('메모 생성 실패:', error);
|
|
alert('메모 생성에 실패했습니다: ' + error.message);
|
|
}
|
|
},
|
|
|
|
skipNoteForHighlight() {
|
|
console.log('📝 메모 입력 건너뛰기');
|
|
this.closeNoteInputModal();
|
|
},
|
|
|
|
// ==================== UI 메서드 위임 ====================
|
|
toggleFeatureMenu(feature) {
|
|
const result = this.uiManager.toggleFeatureMenu(feature);
|
|
this.updateModalStates(); // 상태 동기화
|
|
return result;
|
|
},
|
|
|
|
openNoteModal(highlight = null) {
|
|
const result = this.uiManager.openNoteModal(highlight);
|
|
this.updateModalStates(); // 상태 동기화
|
|
return result;
|
|
},
|
|
|
|
closeNoteModal() {
|
|
const result = this.uiManager.closeNoteModal();
|
|
this.updateModalStates(); // 상태 동기화
|
|
return result;
|
|
},
|
|
|
|
closeLinkModal() {
|
|
const result = this.uiManager.closeLinkModal();
|
|
this.updateModalStates(); // 상태 동기화
|
|
return result;
|
|
},
|
|
|
|
closeBookmarkModal() {
|
|
const result = this.uiManager.closeBookmarkModal();
|
|
this.updateModalStates(); // 상태 동기화
|
|
return result;
|
|
},
|
|
|
|
highlightSearchResults(element, searchText) {
|
|
return this.uiManager.highlightSearchResults(element, searchText);
|
|
},
|
|
|
|
showSuccessMessage(message) {
|
|
return this.uiManager.showSuccessMessage(message);
|
|
},
|
|
|
|
showErrorMessage(message) {
|
|
return this.uiManager.showErrorMessage(message);
|
|
},
|
|
|
|
// ==================== 언어 전환 ====================
|
|
toggleLanguage() {
|
|
this.isKorean = !this.isKorean;
|
|
const lang = this.isKorean ? 'ko' : 'en';
|
|
console.log('🌐 언어 전환:', this.isKorean ? '한국어' : 'English');
|
|
|
|
// 문서에 내장된 언어 전환 기능 찾기 및 실행
|
|
this.findAndExecuteBuiltinLanguageToggle();
|
|
},
|
|
|
|
// 문서에 내장된 언어 전환 기능 찾기
|
|
findAndExecuteBuiltinLanguageToggle() {
|
|
console.log('🔍 문서 내장 언어 전환 기능 찾기 시작');
|
|
|
|
const content = document.getElementById('document-content');
|
|
if (!content) {
|
|
console.warn('❌ document-content 요소를 찾을 수 없습니다');
|
|
return;
|
|
}
|
|
|
|
// 1. 언어 전환 버튼 찾기 (다양한 패턴)
|
|
const buttonSelectors = [
|
|
'button[onclick*="toggleLanguage"]',
|
|
'button[onclick*="language"]',
|
|
'button[onclick*="Language"]',
|
|
'.language-toggle',
|
|
'.lang-toggle',
|
|
'button[id*="lang"]',
|
|
'button[class*="lang"]',
|
|
'input[type="button"][onclick*="language"]'
|
|
];
|
|
|
|
let foundButton = null;
|
|
for (const selector of buttonSelectors) {
|
|
const buttons = content.querySelectorAll(selector);
|
|
if (buttons.length > 0) {
|
|
foundButton = buttons[0];
|
|
console.log(`✅ 언어 전환 버튼 발견 (${selector}):`, foundButton.outerHTML.substring(0, 100));
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 2. 버튼이 있으면 클릭
|
|
if (foundButton) {
|
|
console.log('🔘 내장 언어 전환 버튼 클릭');
|
|
try {
|
|
foundButton.click();
|
|
console.log('✅ 언어 전환 버튼 클릭 완료');
|
|
return;
|
|
} catch (error) {
|
|
console.error('❌ 버튼 클릭 실패:', error);
|
|
}
|
|
}
|
|
|
|
// 3. 버튼이 없으면 스크립트 함수 직접 호출 시도
|
|
this.tryDirectLanguageFunction();
|
|
},
|
|
|
|
// 직접 언어 전환 함수 호출 시도
|
|
tryDirectLanguageFunction() {
|
|
console.log('🔧 직접 언어 전환 함수 호출 시도');
|
|
|
|
const functionNames = [
|
|
'toggleLanguage',
|
|
'changeLanguage',
|
|
'switchLanguage',
|
|
'toggleLang',
|
|
'changeLang'
|
|
];
|
|
|
|
for (const funcName of functionNames) {
|
|
if (typeof window[funcName] === 'function') {
|
|
console.log(`✅ 전역 함수 발견: ${funcName}`);
|
|
try {
|
|
window[funcName]();
|
|
console.log(`✅ ${funcName}() 호출 완료`);
|
|
return;
|
|
} catch (error) {
|
|
console.error(`❌ ${funcName}() 호출 실패:`, error);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 4. 문서 내 스크립트에서 함수 찾기
|
|
this.findLanguageFunctionInScripts();
|
|
},
|
|
|
|
// 문서 내 스크립트에서 언어 전환 함수 찾기
|
|
findLanguageFunctionInScripts() {
|
|
console.log('📜 문서 내 스크립트에서 언어 함수 찾기');
|
|
|
|
const content = document.getElementById('document-content');
|
|
const scripts = content.querySelectorAll('script');
|
|
|
|
console.log(`📜 발견된 스크립트 태그: ${scripts.length}개`);
|
|
|
|
scripts.forEach((script, index) => {
|
|
const scriptContent = script.textContent || script.innerHTML;
|
|
if (scriptContent.includes('language') || scriptContent.includes('Language') || scriptContent.includes('lang')) {
|
|
console.log(`📜 스크립트 ${index + 1}에서 언어 관련 코드 발견:`, scriptContent.substring(0, 200));
|
|
|
|
// 함수 실행 시도
|
|
try {
|
|
eval(scriptContent);
|
|
console.log(`✅ 스크립트 ${index + 1} 실행 완료`);
|
|
} catch (error) {
|
|
console.log(`⚠️ 스크립트 ${index + 1} 실행 실패:`, error.message);
|
|
}
|
|
}
|
|
});
|
|
|
|
console.log('⚠️ 내장 언어 전환 기능을 찾을 수 없습니다');
|
|
},
|
|
|
|
|
|
|
|
async downloadOriginalFile() {
|
|
if (!this.document || !this.document.id) {
|
|
console.warn('문서 정보가 없습니다');
|
|
return;
|
|
}
|
|
|
|
console.log('📕 PDF 다운로드 시도:', {
|
|
id: this.document.id,
|
|
matched_pdf_id: this.document.matched_pdf_id,
|
|
pdf_path: this.document.pdf_path
|
|
});
|
|
|
|
// 1. 현재 문서 자체가 PDF인 경우
|
|
if (this.document.pdf_path) {
|
|
console.log('📄 현재 문서가 PDF - 직접 다운로드');
|
|
this.downloadPdfFile(this.document.pdf_path, this.document.title || 'document');
|
|
return;
|
|
}
|
|
|
|
// 2. 연결된 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);
|
|
}
|
|
},
|
|
|
|
// PDF 파일 직접 다운로드
|
|
downloadPdfFile(pdfPath, filename) {
|
|
try {
|
|
console.log('📄 PDF 파일 직접 다운로드:', pdfPath);
|
|
|
|
// PDF 파일 URL 생성 (상대 경로를 절대 경로로 변환)
|
|
let pdfUrl = pdfPath;
|
|
if (!pdfUrl.startsWith('http')) {
|
|
// 상대 경로인 경우 현재 도메인 기준으로 절대 경로 생성
|
|
const baseUrl = window.location.origin;
|
|
pdfUrl = pdfUrl.startsWith('/') ? baseUrl + pdfUrl : baseUrl + '/' + pdfUrl;
|
|
}
|
|
|
|
console.log('📄 PDF URL:', pdfUrl);
|
|
|
|
// 다운로드 링크 생성 및 클릭
|
|
const link = document.createElement('a');
|
|
link.href = pdfUrl;
|
|
link.download = filename.endsWith('.pdf') ? filename : filename + '.pdf';
|
|
link.target = '_blank'; // 새 탭에서 열기 (다운로드 실패 시 뷰어로 열림)
|
|
|
|
document.body.appendChild(link);
|
|
link.click();
|
|
document.body.removeChild(link);
|
|
|
|
console.log('✅ PDF 다운로드 링크 클릭 완료');
|
|
|
|
} catch (error) {
|
|
console.error('PDF 다운로드 오류:', error);
|
|
alert('PDF 다운로드 중 오류가 발생했습니다: ' + error.message);
|
|
}
|
|
},
|
|
|
|
// ==================== 유틸리티 메서드 ====================
|
|
formatDate(dateString) {
|
|
return new Date(dateString).toLocaleString('ko-KR');
|
|
},
|
|
|
|
formatShortDate(dateString) {
|
|
return new Date(dateString).toLocaleDateString('ko-KR');
|
|
},
|
|
|
|
getColorName(color) {
|
|
const colorNames = {
|
|
'#FFFF00': '노란색',
|
|
'#00FF00': '초록색',
|
|
'#FF0000': '빨간색',
|
|
'#0000FF': '파란색',
|
|
'#FF00FF': '보라색',
|
|
'#00FFFF': '청록색',
|
|
'#FFA500': '주황색',
|
|
'#FFC0CB': '분홍색'
|
|
};
|
|
return colorNames[color] || '기타';
|
|
},
|
|
|
|
getSelectedBookTitle() {
|
|
const selectedBook = this.availableBooks.find(book => book.id === this.linkForm.target_book_id);
|
|
return selectedBook ? selectedBook.title : '서적을 선택하세요';
|
|
},
|
|
|
|
// ==================== 모듈 메서드 위임 ====================
|
|
|
|
// 하이라이트 관련
|
|
selectHighlight(highlightId) {
|
|
return this.highlightManager.selectHighlight(highlightId);
|
|
},
|
|
|
|
deleteHighlight(highlightId) {
|
|
return this.highlightManager.deleteHighlight(highlightId);
|
|
},
|
|
|
|
deleteHighlightsByColor(color, highlightIds) {
|
|
return this.highlightManager.deleteHighlightsByColor(color, highlightIds);
|
|
},
|
|
|
|
deleteAllOverlappingHighlights(highlightIds) {
|
|
return this.highlightManager.deleteAllOverlappingHighlights(highlightIds);
|
|
},
|
|
|
|
hideTooltip() {
|
|
return this.highlightManager.hideTooltip();
|
|
},
|
|
|
|
showAddNoteForm(highlightId) {
|
|
return this.highlightManager.showAddNoteForm(highlightId);
|
|
},
|
|
|
|
deleteNote(noteId) {
|
|
return this.highlightManager.deleteNote(noteId);
|
|
},
|
|
|
|
// 링크 관련
|
|
navigateToLinkedDocument(documentId, linkData) {
|
|
return this.linkManager.navigateToLinkedDocument(documentId, linkData);
|
|
},
|
|
|
|
navigateToBacklinkDocument(documentId, backlinkData) {
|
|
return this.linkManager.navigateToBacklinkDocument(documentId, backlinkData);
|
|
},
|
|
|
|
// 북마크 관련
|
|
scrollToBookmark(bookmark) {
|
|
return this.bookmarkManager.scrollToBookmark(bookmark);
|
|
},
|
|
|
|
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);
|
|
}
|
|
}
|
|
},
|
|
|
|
// 네비게이션 함수들
|
|
goBack() {
|
|
console.log('🔙 뒤로가기');
|
|
window.history.back();
|
|
},
|
|
|
|
navigateToDocument(documentId) {
|
|
if (!documentId) {
|
|
console.warn('⚠️ 문서 ID가 없습니다');
|
|
return;
|
|
}
|
|
console.log('📄 문서로 이동:', documentId);
|
|
window.location.href = `/viewer.html?id=${documentId}`;
|
|
},
|
|
|
|
goToBookContents() {
|
|
if (!this.navigation?.book_info?.id) {
|
|
console.warn('⚠️ 서적 정보가 없습니다');
|
|
return;
|
|
}
|
|
console.log('📚 서적 목차로 이동:', this.navigation.book_info.id);
|
|
window.location.href = `/book-documents.html?book_id=${this.navigation.book_info.id}`;
|
|
}
|
|
});
|
|
|
|
// Alpine.js 컴포넌트 등록
|
|
document.addEventListener('alpine:init', () => {
|
|
console.log('🔧 Alpine.js 컴포넌트 로드됨');
|
|
|
|
// 전역 함수들 (말풍선에서 사용)
|
|
window.cancelTextSelection = () => {
|
|
if (window.documentViewerInstance && window.documentViewerInstance.linkManager) {
|
|
window.documentViewerInstance.linkManager.cancelTextSelection();
|
|
}
|
|
};
|
|
|
|
window.confirmTextSelection = (selectedText, startOffset, endOffset) => {
|
|
if (window.documentViewerInstance && window.documentViewerInstance.linkManager) {
|
|
window.documentViewerInstance.linkManager.confirmTextSelection(selectedText, startOffset, endOffset);
|
|
}
|
|
};
|
|
});
|
|
|
|
// 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 인스턴스가 없습니다');
|
|
}
|
|
}
|
|
});
|
|
});
|