/** * 메모 카드 컴포넌트 */ import { escapeHTML, parseInternalLinks, fixImagePaths, stripMetadata } from '../utils.js'; import { renderAttachmentBox } from './AttachmentBox.js'; import { Constants } from '../utils/Constants.js'; import { I18nManager } from '../utils/I18nManager.js'; /** * 단일 메모 카드의 HTML 생성을 전담합니다. */ export function createMemoCardHtml(memo, isDone) { const cardClass = `memo-card ${isDone ? 'done' : ''} ${memo.is_encrypted ? 'encrypted' : ''} glass-panel`; const borderStyle = memo.color ? `style="border-left: 5px solid ${memo.color}"` : ''; let summaryHtml = ''; if (memo.summary) { // 암호화된 메모가 잠긴 상태라면 AI 요약도 숨김 (정보 유출 방지) const isLocked = memo.is_encrypted && (!memo.content || memo.content.includes('encrypted-block') || typeof memo.is_encrypted === 'number'); // 참고: app.js에서 해독 성공 시 memo.is_encrypted를 false로 바꿨으므로, is_encrypted가 true면 잠긴 상태임 if (!memo.is_encrypted) { summaryHtml = `
${I18nManager.t('label_ai_summary')}: ${escapeHTML(memo.summary)}
`; } } const titleHtml = memo.title ? `

${escapeHTML(memo.title)}

` : ''; let htmlContent = ''; if (!isDone) { if (memo.is_encrypted) { htmlContent = `
🔒 ${I18nManager.t('msg_encrypted_locked')}
`; } else { // 렌더링 시에는 태그와 그룹명을 시각적으로 가립니다 (원본 보존 정책) const displayContent = stripMetadata(memo.content || ''); // marked로 파싱한 후 DOMPurify로 살균하여 XSS 방지 htmlContent = DOMPurify.sanitize(marked.parse(displayContent)); htmlContent = parseInternalLinks(htmlContent); htmlContent = fixImagePaths(htmlContent); } } const contentHtml = `
${htmlContent}
`; let metaHtml = '
'; if (!isDone && memo.group_name && memo.group_name !== Constants.GROUPS.DEFAULT) { const groupName = (Object.values(Constants.GROUPS).includes(memo.group_name)) ? I18nManager.t(`groups.${memo.group_name}`) : memo.group_name; metaHtml += `📁 ${escapeHTML(groupName)}`; } if (memo.tags && memo.tags.length > 0) { memo.tags.forEach(t => { // 암호화된 메모가 잠긴 상태일 때 AI 태그만 선택적으로 숨김 if (memo.is_encrypted && t.source === 'ai') return; const typeClass = t.source === 'ai' ? 'tag-ai' : 'tag-user'; metaHtml += `${t.source === 'ai' ? '🪄 ' : '#'}${escapeHTML(t.name)}`; }); } metaHtml += '
'; let linksHtml = ''; if (!isDone && memo.backlinks && memo.backlinks.length > 0) { linksHtml = `'; } // 암호화된 메모인 경우 해독 전까지 첨부파일 목록 숨김 const attachmentsHtml = !memo.is_encrypted ? renderAttachmentBox(memo.attachments) : ''; // 암호화된 메모가 잠긴 상태라면 하단 액션 버튼(수정, 삭제, AI 등)을 아예 보여주지 않음 (보안 및 UI 겹침 방지) const isLocked = memo.is_encrypted && (!htmlContent || htmlContent.includes('encrypted-block')); const actionsHtml = isLocked ? '' : `
${!isDone ? `` : ''}
`; const idBadge = `
#${memo.id}
`; return { className: cardClass, style: borderStyle, innerHtml: idBadge + summaryHtml + titleHtml + metaHtml + contentHtml + linksHtml + attachmentsHtml + actionsHtml }; }