diff --git a/src/main/resources/static/js/comment-cards.js b/src/main/resources/static/js/comment-cards.js index 3f3b36d..d6da77e 100644 --- a/src/main/resources/static/js/comment-cards.js +++ b/src/main/resources/static/js/comment-cards.js @@ -63,11 +63,23 @@ head.querySelector('.cc-time').textContent = timeAgo(c.publishedAt); head.querySelector('.cc-text').textContent = toPlainText(c.text); - // 카드 자체를 클릭하면 복사 (버튼이 캡처 이미지에 찍히지 않도록 버튼 제거) - card.title = '클릭하면 이미지 복사'; + // 카드 자체를 클릭하면 복사 (클립보드) + card.title = '클릭하면 이미지 복사 · 저장 버튼은 PNG 파일로 내려받기'; card.addEventListener('click', () => window.copyCard(card)); + // 저장(다운로드) 버튼 — cc-noexport라 캡처 이미지엔 안 찍힘. 캡컷엔 이 파일을 불러오기. + const actions = document.createElement('div'); + actions.className = 'cc-actions cc-noexport'; + const saveBtn = document.createElement('button'); + saveBtn.type = 'button'; + saveBtn.className = 'cc-iconbtn'; + saveBtn.textContent = '⬇ 저장'; + saveBtn.title = 'PNG 파일로 저장 (캡컷에서 불러오기)'; + saveBtn.addEventListener('click', (e) => { e.stopPropagation(); window.downloadCard(card, c); }); + actions.appendChild(saveBtn); + card.appendChild(head); + card.appendChild(actions); grid.appendChild(card); }); if (window.lucide) window.lucide.createIcons(); @@ -78,7 +90,12 @@ const ms = window.modernScreenshot; if (!ms || !ms.domToBlob) throw new Error('캡처 라이브러리 로드 실패'); // backgroundColor:null → 카드 바깥 투명. scale 2 → 또렷하게. - return await ms.domToBlob(cardEl, { backgroundColor: null, scale: 2 }); + // filter: .cc-noexport(저장/액션 버튼)는 캡처 이미지에서 제외 + return await ms.domToBlob(cardEl, { + backgroundColor: null, + scale: 2, + filter: (node) => !(node instanceof Element && node.classList.contains('cc-noexport')), + }); } function downloadBlob(blob) { @@ -104,6 +121,29 @@ toastTimer = setTimeout(() => t.classList.remove('show'), 1500); } + function buildFileName(c) { + var who = (c && c.authorName) ? c.authorName.replace(/[^\w가-힣]+/g, '').slice(0, 20) : 'card'; + return 'comment-' + (who || 'card') + '.png'; + } + + window.downloadCard = async function (cardEl, c) { + if (cardEl.dataset.busy === '1') return; + cardEl.dataset.busy = '1'; + try { + const blob = await captureBlob(cardEl); + const a = document.createElement('a'); + a.href = URL.createObjectURL(blob); + a.download = buildFileName(c); + a.click(); + URL.revokeObjectURL(a.href); + showToast('저장됨 ⬇ (캡컷에서 불러오기)'); + } catch (e) { + showToast('저장 실패'); + } finally { + setTimeout(() => { delete cardEl.dataset.busy; }, 600); + } + }; + window.copyCard = async function (cardEl) { if (cardEl.dataset.copying === '1') return; // 중복 클릭 방지 cardEl.dataset.copying = '1'; diff --git a/src/main/resources/templates/comment-cards.html b/src/main/resources/templates/comment-cards.html index bf34c1b..04a141a 100644 --- a/src/main/resources/templates/comment-cards.html +++ b/src/main/resources/templates/comment-cards.html @@ -27,6 +27,11 @@ .cc-text { font-size:14px; color:var(--text); margin-top:.35rem; white-space:pre-wrap; word-break:break-word; line-height:1.45; } .cc-stats { font-size:12px; color:var(--text-3); margin-top:.5rem; display:flex; gap:1rem; } .comment-card { cursor:pointer; } + /* 저장 버튼 — cc-noexport라 캡처 이미지에서 제외됨 */ + .cc-actions { position:absolute; top:.5rem; right:.5rem; display:flex; gap:.3rem; } + .cc-iconbtn { background:var(--surface-2); color:var(--text-2); border:1px solid var(--border); + border-radius:6px; font-size:11.5px; font-weight:600; padding:.25rem .5rem; cursor:pointer; line-height:1; } + .cc-iconbtn:hover { color:var(--text); border-color:var(--primary, #6366f1); } /* 복사 완료 토스트 — 카드 밖(body)에 떠서 캡처 이미지에 안 찍힘 */ .cc-toast { position:fixed; left:50%; bottom:2rem; transform:translateX(-50%) translateY(1rem); background:var(--text); color:var(--surface); padding:.6rem 1.1rem; border-radius:999px; @@ -42,7 +47,7 @@
- 유튜브 링크의 댓글을 가져와 카드로 만들고, 프로필·아이디를 모자이크하세요. 카드를 클릭하면 이미지가 복사됩니다(캡컷 등에 Ctrl+V). + 유튜브 링크의 댓글을 가져와 카드로 만들고, 프로필·아이디를 모자이크하세요. 카드 클릭=이미지 복사, ⬇ 저장=PNG 파일 다운로드(캡컷은 클립보드 붙여넣기가 안 되니 저장한 파일을 "불러오기" 하세요).