feat(ui): 썸네일 클릭 미리보기를 칸반·채널상세·프로덕션상세에 추가
수집함·발굴·대시보드처럼, 썸네일 있는 나머지 화면에서도 썸네일을 클릭하면 YouTube 9:16 미리보기 팝업이 뜨도록 통일. - 칸반(board): 카드 썸네일 클릭(드래그와 분리 위해 stopPropagation) - 채널상세(channel_detail): 영상 목록 썸네일(제목 링크는 YouTube 유지) - 프로덕션상세(production_detail): videoUrl에서 11자리 id 추출해 임베드, id 없으면 새 탭 폴백 각 페이지에 동일 모달(배경/ESC 닫힘, 닫을 때 iframe 비워 재생 중지) 추가. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
c7ce8de90e
commit
c0ade287c2
@ -44,7 +44,19 @@
|
||||
<!-- columns injected -->
|
||||
</div>
|
||||
|
||||
<!-- 영상 미리보기 모달 -->
|
||||
<div id="videoModal" style="display:none; position:fixed; z-index:1000; inset:0; background:rgba(0,0,0,0.85); align-items:center; justify-content:center; padding:20px;" onclick="if(event.target===this) closeVideoModal()">
|
||||
<div style="background:var(--surface); border:1px solid var(--border); border-radius:12px; overflow:hidden;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; gap:14px; padding:11px 14px; border-bottom:1px solid var(--border);">
|
||||
<span id="vmTitle" class="text-sm font-bold truncate" style="max-width:300px;">미리보기</span>
|
||||
<button onclick="closeVideoModal()" class="text-muted hover:text-white" style="font-size:24px; line-height:1; background:none; border:none; cursor:pointer;">×</button>
|
||||
</div>
|
||||
<div id="vmFrame" style="width:min(360px,90vw); aspect-ratio:9/16; max-height:78vh; background:#000;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
#vmFrame iframe { width:100%; height:100%; border:0; display:block; }
|
||||
.kb-col { background: var(--surface-2); border:1px solid var(--border); border-radius: var(--r); display:flex; flex-direction:column; min-height:200px; }
|
||||
.kb-col-head { padding:12px; border-bottom:1px solid var(--border); display:flex; align-items:center; justify-content:space-between; position:sticky; top:0; background:var(--surface-2); border-radius:var(--r) var(--r) 0 0; }
|
||||
.kb-body { padding:10px; display:flex; flex-direction:column; gap:8px; min-height:120px; flex:1; transition: background 0.15s; }
|
||||
@ -94,7 +106,9 @@
|
||||
].filter(Boolean).join(' · ');
|
||||
return `<div class="kb-card" draggable="true" data-id="${v.id}">
|
||||
<div style="display:flex; gap:8px;">
|
||||
<img src="${esc(v.thumbnailUrl)}" style="width:64px; height:36px; object-fit:cover; border-radius:4px; flex-shrink:0;">
|
||||
<img src="${esc(v.thumbnailUrl)}" title="미리보기" data-vid="${v.videoId}" data-title="${esc(v.title)}"
|
||||
onclick="event.stopPropagation(); openVideoModal(this.dataset.vid, this.dataset.title)"
|
||||
style="width:64px; height:36px; object-fit:cover; border-radius:4px; flex-shrink:0; cursor:pointer;">
|
||||
<div style="min-width:0; flex:1;">
|
||||
<div class="text-sm font-bold" style="display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; line-height:1.3;">${esc(v.title)}</div>
|
||||
</div>
|
||||
@ -184,7 +198,17 @@
|
||||
|
||||
function openHelp(){ document.getElementById('helpModal').classList.add('open'); }
|
||||
function closeHelp(){ document.getElementById('helpModal').classList.remove('open'); }
|
||||
document.addEventListener('keydown', e => { if(e.key === 'Escape') closeHelp(); });
|
||||
|
||||
function openVideoModal(vid, title){
|
||||
document.getElementById('vmTitle').textContent = title || '미리보기';
|
||||
document.getElementById('vmFrame').innerHTML = '<iframe src="https://www.youtube.com/embed/'+vid+'?autoplay=1" allow="autoplay; encrypted-media" allowfullscreen></iframe>';
|
||||
document.getElementById('videoModal').style.display = 'flex';
|
||||
}
|
||||
function closeVideoModal(){
|
||||
document.getElementById('vmFrame').innerHTML = '';
|
||||
document.getElementById('videoModal').style.display = 'none';
|
||||
}
|
||||
document.addEventListener('keydown', e => { if(e.key === 'Escape'){ closeHelp(); closeVideoModal(); } });
|
||||
|
||||
loadBoard();
|
||||
/*]]>*/
|
||||
|
||||
@ -97,11 +97,12 @@
|
||||
style="border-bottom: 1px solid var(--glass-border); transition: background 0.2s;">
|
||||
<td class="p-4">
|
||||
<div class="flex items-center gap-4">
|
||||
<a th:href="'https://www.youtube.com/watch?v=' + ${video.videoId}" target="_blank"
|
||||
class="block relative group">
|
||||
<div class="block relative group" style="cursor:pointer;" title="미리보기"
|
||||
th:attr="data-vid=${video.videoId},data-title=${video.title}"
|
||||
onclick="openVideoModal(this.dataset.vid, this.dataset.title)">
|
||||
<img th:src="${video.thumbnailUrl}" alt="Thumb"
|
||||
style="width: 120px; height: 68px; object-fit: cover; border-radius: 6px;">
|
||||
</a>
|
||||
</div>
|
||||
<a th:href="'https://www.youtube.com/watch?v=' + ${video.videoId}" target="_blank"
|
||||
class="font-bold text-sm hover:text-[var(--primary)] transition-colors"
|
||||
style="max-width: 400px; line-height: 1.4;" th:text="${video.title}">Video Title</a>
|
||||
@ -337,8 +338,32 @@
|
||||
finally { btn.disabled = false; btn.innerHTML = orig; if (window.lucide) lucide.createIcons(); }
|
||||
}
|
||||
|
||||
// ----- 영상 미리보기 -----
|
||||
function openVideoModal(vid, title){
|
||||
document.getElementById('vmTitle').textContent = title || '미리보기';
|
||||
document.getElementById('vmFrame').innerHTML = '<iframe src="https://www.youtube.com/embed/'+vid+'?autoplay=1" allow="autoplay; encrypted-media" allowfullscreen></iframe>';
|
||||
document.getElementById('videoModal').style.display = 'flex';
|
||||
}
|
||||
function closeVideoModal(){
|
||||
document.getElementById('vmFrame').innerHTML = '';
|
||||
document.getElementById('videoModal').style.display = 'none';
|
||||
}
|
||||
document.addEventListener('keydown', e => { if(e.key === 'Escape') closeVideoModal(); });
|
||||
|
||||
loadGrowth();
|
||||
</script>
|
||||
|
||||
<!-- 영상 미리보기 모달 -->
|
||||
<div id="videoModal" style="display:none; position:fixed; z-index:1000; inset:0; background:rgba(0,0,0,0.85); align-items:center; justify-content:center; padding:20px;" onclick="if(event.target===this) closeVideoModal()">
|
||||
<div style="background:var(--surface); border:1px solid var(--border); border-radius:12px; overflow:hidden;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; gap:14px; padding:11px 14px; border-bottom:1px solid var(--border);">
|
||||
<span id="vmTitle" class="text-sm font-bold truncate" style="max-width:300px;">미리보기</span>
|
||||
<button onclick="closeVideoModal()" class="text-muted hover:text-white" style="font-size:24px; line-height:1; background:none; border:none; cursor:pointer;">×</button>
|
||||
</div>
|
||||
<div id="vmFrame" style="width:min(360px,90vw); aspect-ratio:9/16; max-height:78vh; background:#000;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<style>#vmFrame iframe { width:100%; height:100%; border:0; display:block; }</style>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
|
||||
@ -77,15 +77,16 @@
|
||||
<td class="p-4" data-label="Video">
|
||||
<div class="flex items-center gap-4">
|
||||
<!-- Thumbnail (toggleable) -->
|
||||
<a th:href="${video.videoUrl}" target="_blank"
|
||||
class="block relative group thumbnail-item" style="display: none;">
|
||||
<div class="block relative group thumbnail-item" style="display: none; cursor:pointer;" title="미리보기"
|
||||
th:attr="data-url=${video.videoUrl},data-title=${video.title}"
|
||||
onclick="openVideoModalUrl(this.dataset.url, this.dataset.title)">
|
||||
<img th:src="${video.thumbnailUrl}" alt="Thumb"
|
||||
style="width: 120px; height: 68px; object-fit: cover; border-radius: 6px;">
|
||||
<div class="absolute inset-0 bg-black/50 flex items-center justify-center opacity-0 group-hover:opacity-100 transition-opacity"
|
||||
style="border-radius: 6px;">
|
||||
<i data-lucide="external-link" class="text-white w-6 h-6"></i>
|
||||
<i data-lucide="play" class="text-white w-6 h-6"></i>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
<div class="flex flex-col gap-1">
|
||||
<a th:href="${video.videoUrl}" target="_blank"
|
||||
class="font-bold text-sm hover:text-[var(--primary)] transition-colors"
|
||||
@ -1006,7 +1007,38 @@
|
||||
alert('Error saving script.');
|
||||
}
|
||||
}
|
||||
|
||||
// ----- 영상 미리보기 (videoUrl → id 추출) -----
|
||||
function ytId(url){
|
||||
if(!url) return null;
|
||||
const m = String(url).match(/(?:v=|\/embed\/|youtu\.be\/|\/shorts\/)([0-9A-Za-z_-]{11})/);
|
||||
return m ? m[1] : null;
|
||||
}
|
||||
function openVideoModalUrl(url, title){
|
||||
const id = ytId(url);
|
||||
if(!id){ window.open(url, '_blank'); return; } // id 못찾으면 새 탭으로 폴백
|
||||
document.getElementById('vmTitle').textContent = title || '미리보기';
|
||||
document.getElementById('vmFrame').innerHTML = '<iframe src="https://www.youtube.com/embed/'+id+'?autoplay=1" allow="autoplay; encrypted-media" allowfullscreen></iframe>';
|
||||
document.getElementById('videoModal').style.display = 'flex';
|
||||
}
|
||||
function closeVideoModal(){
|
||||
document.getElementById('vmFrame').innerHTML = '';
|
||||
document.getElementById('videoModal').style.display = 'none';
|
||||
}
|
||||
document.addEventListener('keydown', e => { if(e.key === 'Escape') closeVideoModal(); });
|
||||
</script>
|
||||
|
||||
<!-- 영상 미리보기 모달 -->
|
||||
<div id="videoModal" style="display:none; position:fixed; z-index:1000; inset:0; background:rgba(0,0,0,0.85); align-items:center; justify-content:center; padding:20px;" onclick="if(event.target===this) closeVideoModal()">
|
||||
<div style="background:var(--surface); border:1px solid var(--border); border-radius:12px; overflow:hidden;">
|
||||
<div style="display:flex; justify-content:space-between; align-items:center; gap:14px; padding:11px 14px; border-bottom:1px solid var(--border);">
|
||||
<span id="vmTitle" class="text-sm font-bold truncate" style="max-width:300px;">미리보기</span>
|
||||
<button onclick="closeVideoModal()" class="text-muted hover:text-white" style="font-size:24px; line-height:1; background:none; border:none; cursor:pointer;">×</button>
|
||||
</div>
|
||||
<div id="vmFrame" style="width:min(360px,90vw); aspect-ratio:9/16; max-height:78vh; background:#000;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<style>#vmFrame iframe { width:100%; height:100%; border:0; display:block; }</style>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user