feat(comment-cards): 카드 PNG 파일 저장(다운로드) 버튼 추가
캡컷은 클립보드 이미지 붙여넣기를 못 받아 파일 불러오기가 필요 → 카드별 '⬇ 저장' 버튼으로 PNG 다운로드 제공. 저장 버튼은 cc-noexport+modern-screenshot filter로 캡처 이미지에서 제외. 카드 클릭=클립보드 복사는 유지. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7de8f954ad
commit
ba3776eb7e
@ -63,11 +63,23 @@
|
|||||||
head.querySelector('.cc-time').textContent = timeAgo(c.publishedAt);
|
head.querySelector('.cc-time').textContent = timeAgo(c.publishedAt);
|
||||||
head.querySelector('.cc-text').textContent = toPlainText(c.text);
|
head.querySelector('.cc-text').textContent = toPlainText(c.text);
|
||||||
|
|
||||||
// 카드 자체를 클릭하면 복사 (버튼이 캡처 이미지에 찍히지 않도록 버튼 제거)
|
// 카드 자체를 클릭하면 복사 (클립보드)
|
||||||
card.title = '클릭하면 이미지 복사';
|
card.title = '클릭하면 이미지 복사 · 저장 버튼은 PNG 파일로 내려받기';
|
||||||
card.addEventListener('click', () => window.copyCard(card));
|
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(head);
|
||||||
|
card.appendChild(actions);
|
||||||
grid.appendChild(card);
|
grid.appendChild(card);
|
||||||
});
|
});
|
||||||
if (window.lucide) window.lucide.createIcons();
|
if (window.lucide) window.lucide.createIcons();
|
||||||
@ -78,7 +90,12 @@
|
|||||||
const ms = window.modernScreenshot;
|
const ms = window.modernScreenshot;
|
||||||
if (!ms || !ms.domToBlob) throw new Error('캡처 라이브러리 로드 실패');
|
if (!ms || !ms.domToBlob) throw new Error('캡처 라이브러리 로드 실패');
|
||||||
// backgroundColor:null → 카드 바깥 투명. scale 2 → 또렷하게.
|
// 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) {
|
function downloadBlob(blob) {
|
||||||
@ -104,6 +121,29 @@
|
|||||||
toastTimer = setTimeout(() => t.classList.remove('show'), 1500);
|
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) {
|
window.copyCard = async function (cardEl) {
|
||||||
if (cardEl.dataset.copying === '1') return; // 중복 클릭 방지
|
if (cardEl.dataset.copying === '1') return; // 중복 클릭 방지
|
||||||
cardEl.dataset.copying = '1';
|
cardEl.dataset.copying = '1';
|
||||||
|
|||||||
@ -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-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; }
|
.cc-stats { font-size:12px; color:var(--text-3); margin-top:.5rem; display:flex; gap:1rem; }
|
||||||
.comment-card { cursor:pointer; }
|
.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)에 떠서 캡처 이미지에 안 찍힘 */
|
/* 복사 완료 토스트 — 카드 밖(body)에 떠서 캡처 이미지에 안 찍힘 */
|
||||||
.cc-toast { position:fixed; left:50%; bottom:2rem; transform:translateX(-50%) translateY(1rem);
|
.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;
|
background:var(--text); color:var(--surface); padding:.6rem 1.1rem; border-radius:999px;
|
||||||
@ -42,7 +47,7 @@
|
|||||||
<div layout:fragment="content">
|
<div layout:fragment="content">
|
||||||
<h1 style="font-size:1.5rem;font-weight:700;margin-bottom:.3rem;">댓글 카드</h1>
|
<h1 style="font-size:1.5rem;font-weight:700;margin-bottom:.3rem;">댓글 카드</h1>
|
||||||
<p style="color:var(--text-3);margin-bottom:1.2rem;font-size:14px;">
|
<p style="color:var(--text-3);margin-bottom:1.2rem;font-size:14px;">
|
||||||
유튜브 링크의 댓글을 가져와 카드로 만들고, 프로필·아이디를 모자이크하세요. <b>카드를 클릭하면 이미지가 복사</b>됩니다(캡컷 등에 Ctrl+V).
|
유튜브 링크의 댓글을 가져와 카드로 만들고, 프로필·아이디를 모자이크하세요. <b>카드 클릭=이미지 복사</b>, <b>⬇ 저장=PNG 파일 다운로드</b>(캡컷은 클립보드 붙여넣기가 안 되니 저장한 파일을 "불러오기" 하세요).
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="cc-toolbar">
|
<div class="cc-toolbar">
|
||||||
@ -71,7 +76,7 @@
|
|||||||
<th:block layout:fragment="script">
|
<th:block layout:fragment="script">
|
||||||
<!-- DOM→이미지 캡처 (Task 5에서 사용). blur 필터 지원 위해 modern-screenshot 사용. -->
|
<!-- DOM→이미지 캡처 (Task 5에서 사용). blur 필터 지원 위해 modern-screenshot 사용. -->
|
||||||
<script src="https://cdn.jsdelivr.net/npm/modern-screenshot@4/dist/index.js"></script>
|
<script src="https://cdn.jsdelivr.net/npm/modern-screenshot@4/dist/index.js"></script>
|
||||||
<script th:src="@{/js/comment-cards.js(v=20260629c)}"></script>
|
<script th:src="@{/js/comment-cards.js(v=20260630)}"></script>
|
||||||
</th:block>
|
</th:block>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user