/**
* 모달 창(메모 상세, 파일 라이브러리 등) 생성을 관리하는 모듈
*/
import { API } from '../api.js';
import { escapeHTML } from '../utils.js';
import { renderAttachmentBox } from './AttachmentBox.js';
import { I18nManager } from '../utils/I18nManager.js';
import { Constants } from '../utils/Constants.js';
export const ModalManager = {
// 타이밍 이슈 방지를 위해 lazy getter 패턴 적용
getDOM() {
return {
modal: document.getElementById('memoModal'),
modalContent: document.getElementById('modalContent'),
loadingOverlay: document.getElementById('loadingOverlay'),
explorerModal: document.getElementById('explorerModal'),
explorerContent: document.getElementById('explorerContent')
};
},
/**
* 전체 첨부파일 라이브러리(Asset Library) 모달 열기
*/
async openAssetLibrary(openMemoDetailsCallback) {
const dom = this.getDOM();
if (!dom.loadingOverlay) return;
dom.loadingOverlay.style.display = 'flex';
try {
const assets = await API.fetchAssets();
let html = `
${I18nManager.t('label_asset_management')}
${I18nManager.t('label_asset_hint')}
${assets.length > 0 ? assets.map(a => `
${['png','jpg','jpeg','gif','webp','svg'].includes(a.file_type?.toLowerCase())
? `

`
: `
📎
`
}
${escapeHTML(a.original_name)}
${a.memo_title ? `${I18nManager.t('label_memo_ref')}${escapeHTML(a.memo_title)}` : I18nManager.t('label_no_memo_ref')}
`).join('') : `
${I18nManager.t('label_no_assets')}
`}
`;
dom.modalContent.innerHTML = html;
dom.modal.classList.add('active');
// 닫기 버튼 이벤트
dom.modalContent.querySelector('.close-modal-btn').onclick = () => {
dom.modal.classList.remove('active');
};
dom.modalContent.querySelectorAll('.asset-card').forEach(card => {
card.onclick = (e) => {
const url = card.dataset.url;
const filename = url.split('/').pop();
const originalName = card.querySelector('div').innerText;
const memoId = card.dataset.memoId;
if (e.altKey) {
e.stopPropagation();
window.downloadFile(filename, originalName);
} else if (memoId && memoId !== 'null') {
dom.modal.classList.remove('active');
openMemoDetailsCallback(memoId, window.allMemosCache);
} else {
window.downloadFile(filename, originalName);
}
};
});
} catch (err) { alert(err.message); }
finally { dom.loadingOverlay.style.display = 'none'; }
},
/**
* 지식 탐색기(Knowledge Explorer) 모달 열기
*/
openKnowledgeExplorer(memos, activeFilter, onFilterCallback) {
const dom = this.getDOM();
// 1. 그룹 및 태그 카운트 계산
const groupAllKey = 'all';
const groupCounts = { [groupAllKey]: memos.length };
const tagCounts = {};
const tagsSourceMap = new Map(); // 태그명 -> 소스 매핑
memos.forEach(m => {
const g = m.group_name || Constants.GROUPS.DEFAULT;
groupCounts[g] = (groupCounts[g] || 0) + 1;
if (m.tags) {
m.tags.forEach(t => {
tagCounts[t.name] = (tagCounts[t.name] || 0) + 1;
const current = tagsSourceMap.get(t.name);
if (!current || t.source === 'user') tagsSourceMap.set(t.name, t.source);
});
}
});
const sortedGroups = Object.keys(groupCounts)
.filter(g => g !== groupAllKey)
.sort((a,b) => a === Constants.GROUPS.DEFAULT ? -1 : b === Constants.GROUPS.DEFAULT ? 1 : a.localeCompare(b));
const sortedTags = Object.keys(tagCounts).sort().map(tn => ({
name: tn,
source: tagsSourceMap.get(tn),
count: tagCounts[tn]
}));
let html = `
${I18nManager.t('label_group_explorer')}
💡 ${I18nManager.t('nav_all')} ${groupCounts[groupAllKey]}
${sortedGroups.map(g => `
📁 ${escapeHTML(g)} ${groupCounts[g]}
`).join('')}
${I18nManager.t('label_tag_explorer')}
${sortedTags.map(t => `
${t.source === 'ai' ? '🪄' : '🏷️'} ${escapeHTML(t.name)} ${t.count}
`).join('')}
`;
dom.explorerContent.innerHTML = html;
dom.explorerModal.classList.add('active');
// 이벤트 바인딩
const closeBtn = dom.explorerModal.querySelector('.close-explorer-btn');
closeBtn.onclick = () => dom.explorerModal.classList.remove('active');
dom.explorerContent.querySelectorAll('.explorer-chip').forEach(chip => {
chip.onclick = () => {
const filter = chip.dataset.filter;
onFilterCallback(filter);
dom.explorerModal.classList.remove('active');
};
});
},
/**
* 개별 메모 상세 모달 열기
*/
openMemoModal(id, memos) {
const dom = this.getDOM();
const memo = memos.find(m => m.id == id);
if (!memo) return;
import('../utils.js').then(({ parseInternalLinks, fixImagePaths, stripMetadata }) => {
// 렌더링 시에는 태그와 그룹명을 시각적으로 가립니다 (원본 보존 정책)
const displayContent = stripMetadata(memo.content || '');
let html = DOMPurify.sanitize(marked.parse(displayContent));
html = parseInternalLinks(html);
html = fixImagePaths(html);
const lastUpdatedTime = new Date(memo.updated_at).toLocaleString();
dom.modalContent.innerHTML = `
${memo.title ? `${escapeHTML(memo.title)}
` : ''}
${memo.summary ? `
🪄 AI INSIGHT
${escapeHTML(memo.summary)}
` : '
'}
${html}
${I18nManager.t('label_last_updated')}${lastUpdatedTime}
`;
// 닫기 버튼 이벤트
const closeBtn = dom.modalContent.querySelector('.close-modal-btn');
if (closeBtn) {
closeBtn.onclick = () => dom.modal.classList.remove('active');
}
const attachmentsHtml = renderAttachmentBox(memo.attachments);
if (attachmentsHtml) {
const footer = document.createElement('div');
footer.style.cssText = 'margin-top:30px; padding-top:15px; border-top:1px solid rgba(255,255,255,0.05);';
footer.innerHTML = attachmentsHtml;
dom.modalContent.appendChild(footer);
}
dom.modal.classList.add('active');
dom.modalContent.querySelectorAll('.internal-link').forEach(l => {
l.onclick = () => this.openMemoModal(l.dataset.id, memos);
});
});
}
};