diff --git a/public/folder.js b/public/folder.js index 4da78e5..7ee26f3 100644 --- a/public/folder.js +++ b/public/folder.js @@ -30,6 +30,8 @@ var action = btn.getAttribute('data-action') if (action === 'edit') { location.href = '/op/folder/' + encodeURIComponent(folder) + '/video/editor?id=' + encodeURIComponent(targetId) + } else if (action === 'copyUrl') { + copyVideoUrl(targetId) } else if (action === 'rename') { var t = window.prompt('새 영상 제목', targetTitle) if (t && t !== targetTitle) { @@ -56,4 +58,44 @@ } hideCtx() }) + + function copyVideoUrl(id) { + var url = location.origin + '/player/' + encodeURIComponent(id) + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(url).then(function () { + flashToast('주소가 복사되었습니다.') + }, function () { + fallbackCopy(url) + }) + } else { + fallbackCopy(url) + } + } + function fallbackCopy(text) { + try { + var ta = document.createElement('textarea') + ta.value = text + ta.style.position = 'fixed' + ta.style.left = '-9999px' + document.body.appendChild(ta) + ta.focus(); ta.select() + var ok = document.execCommand && document.execCommand('copy') + document.body.removeChild(ta) + if (ok) flashToast('주소가 복사되었습니다.') + else window.prompt('주소를 복사하세요:', text) + } catch (e) { + window.prompt('주소를 복사하세요:', text) + } + } + function flashToast(msg) { + var el = document.createElement('div') + el.className = 'flashToast' + el.textContent = msg + document.body.appendChild(el) + requestAnimationFrame(function () { el.classList.add('show') }) + setTimeout(function () { + el.classList.remove('show') + setTimeout(function () { el.parentNode && el.parentNode.removeChild(el) }, 200) + }, 1500) + } })() diff --git a/public/public-folder.js b/public/public-folder.js new file mode 100644 index 0000000..d8af3cf --- /dev/null +++ b/public/public-folder.js @@ -0,0 +1,75 @@ +(function () { + var ctxMenu = document.getElementById('ctxMenu') + if (!ctxMenu) return + var targetId = null + + function showCtx(x, y) { + ctxMenu.style.left = x + 'px' + ctxMenu.style.top = y + 'px' + ctxMenu.hidden = false + } + function hideCtx() { ctxMenu.hidden = true } + + document.querySelectorAll('.videoCard').forEach(function (card) { + card.addEventListener('contextmenu', function (e) { + e.preventDefault() + targetId = card.getAttribute('data-video-id') || card.getAttribute('data-id') + showCtx(e.clientX, e.clientY) + }) + }) + + document.addEventListener('click', function (e) { + if (!ctxMenu.contains(e.target)) hideCtx() + }) + document.addEventListener('scroll', hideCtx, true) + + ctxMenu.addEventListener('click', function (e) { + var btn = e.target.closest('button') + if (!btn) return + var action = btn.getAttribute('data-action') + if (action === 'copyUrl' && targetId) { + copyVideoUrl(targetId) + } + hideCtx() + }) + + function copyVideoUrl(id) { + var url = location.origin + '/player/' + encodeURIComponent(id) + if (navigator.clipboard && navigator.clipboard.writeText) { + navigator.clipboard.writeText(url).then(function () { + flashToast('주소가 복사되었습니다.') + }, function () { + fallbackCopy(url) + }) + } else { + fallbackCopy(url) + } + } + function fallbackCopy(text) { + try { + var ta = document.createElement('textarea') + ta.value = text + ta.style.position = 'fixed' + ta.style.left = '-9999px' + document.body.appendChild(ta) + ta.focus(); ta.select() + var ok = document.execCommand && document.execCommand('copy') + document.body.removeChild(ta) + if (ok) flashToast('주소가 복사되었습니다.') + else window.prompt('주소를 복사하세요:', text) + } catch (e) { + window.prompt('주소를 복사하세요:', text) + } + } + function flashToast(msg) { + var el = document.createElement('div') + el.className = 'flashToast' + el.textContent = msg + document.body.appendChild(el) + requestAnimationFrame(function () { el.classList.add('show') }) + setTimeout(function () { + el.classList.remove('show') + setTimeout(function () { el.parentNode && el.parentNode.removeChild(el) }, 200) + }, 1500) + } +})() diff --git a/public/styles.css b/public/styles.css index 037ffee..b3deb94 100644 --- a/public/styles.css +++ b/public/styles.css @@ -146,6 +146,17 @@ body.siteBody.centerLayout { display: flex; align-items: center; justify-content } .ctxMenu button:hover { background: var(--bg-alt); } +.flashToast { + position: fixed; left: 50%; bottom: 32px; transform: translate(-50%, 12px); + background: var(--bg-card); color: var(--text); + border: 1px solid var(--border); border-radius: 8px; + padding: 10px 18px; font-size: 14px; + box-shadow: 0 8px 20px rgba(0,0,0,0.4); + opacity: 0; transition: opacity 180ms ease, transform 180ms ease; + z-index: 300; pointer-events: none; +} +.flashToast.show { opacity: 1; transform: translate(-50%, 0); } + /* modal */ .modalOverlay { position: fixed; inset: 0; background: rgba(0,0,0,0.6); diff --git a/views/folder.ejs b/views/folder.ejs index 1ff0072..c51f353 100644 --- a/views/folder.ejs +++ b/views/folder.ejs @@ -42,9 +42,14 @@ + + + diff --git a/views/op/folder.ejs b/views/op/folder.ejs index e04d286..b7b623e 100644 --- a/views/op/folder.ejs +++ b/views/op/folder.ejs @@ -39,6 +39,7 @@