Download functionality
This commit is contained in:
@@ -98,6 +98,11 @@ App.videos = App.videos || {};
|
||||
const uploaderText = v.uploader || v.channel || '';
|
||||
card.innerHTML = `
|
||||
<button class="favorite-btn" type="button" aria-pressed="false" aria-label="Add to favorites" data-fav-key="${favoriteKey || ''}">♡</button>
|
||||
<button class="video-menu-btn" type="button" aria-haspopup="true" aria-expanded="false" aria-label="More options">⋯</button>
|
||||
<div class="video-menu" role="menu">
|
||||
<button class="video-menu-item" type="button" data-action="info" role="menuitem">Show info</button>
|
||||
<button class="video-menu-item" type="button" data-action="download" role="menuitem">Download</button>
|
||||
</div>
|
||||
<img src="${v.thumb}" alt="${v.title}">
|
||||
<h4>${v.title}</h4>
|
||||
${uploaderText ? `<p class="video-meta"><button class="uploader-link" type="button" data-uploader="${uploaderText}">${uploaderText}</button></p>` : ''}
|
||||
@@ -119,6 +124,30 @@ App.videos = App.videos || {};
|
||||
App.videos.handleSearch(uploader);
|
||||
};
|
||||
}
|
||||
const menuBtn = card.querySelector('.video-menu-btn');
|
||||
const menu = card.querySelector('.video-menu');
|
||||
const showInfoBtn = card.querySelector('.video-menu-item[data-action="info"]');
|
||||
const downloadBtn = card.querySelector('.video-menu-item[data-action="download"]');
|
||||
if (menuBtn && menu) {
|
||||
menuBtn.onclick = (event) => {
|
||||
event.stopPropagation();
|
||||
App.videos.toggleMenu(menu, menuBtn);
|
||||
};
|
||||
}
|
||||
if (showInfoBtn) {
|
||||
showInfoBtn.onclick = (event) => {
|
||||
event.stopPropagation();
|
||||
App.ui.showInfo(v);
|
||||
App.videos.closeAllMenus();
|
||||
};
|
||||
}
|
||||
if (downloadBtn) {
|
||||
downloadBtn.onclick = (event) => {
|
||||
event.stopPropagation();
|
||||
App.videos.downloadVideo(v);
|
||||
App.videos.closeAllMenus();
|
||||
};
|
||||
}
|
||||
card.onclick = () => App.player.open(v.url);
|
||||
grid.appendChild(card);
|
||||
state.renderedVideoIds.add(v.id);
|
||||
@@ -174,4 +203,49 @@ App.videos = App.videos || {};
|
||||
loadMoreBtn.disabled = state.isLoading || !state.hasNextPage;
|
||||
loadMoreBtn.style.display = state.hasNextPage ? 'flex' : 'none';
|
||||
};
|
||||
|
||||
// Context menu helpers for per-card actions.
|
||||
App.videos.closeAllMenus = function() {
|
||||
document.querySelectorAll('.video-menu.open').forEach((menu) => {
|
||||
menu.classList.remove('open');
|
||||
});
|
||||
document.querySelectorAll('.video-menu-btn[aria-expanded="true"]').forEach((btn) => {
|
||||
btn.setAttribute('aria-expanded', 'false');
|
||||
});
|
||||
};
|
||||
|
||||
App.videos.toggleMenu = function(menu, button) {
|
||||
const isOpen = menu.classList.contains('open');
|
||||
App.videos.closeAllMenus();
|
||||
if (!isOpen) {
|
||||
menu.classList.add('open');
|
||||
if (button) {
|
||||
button.setAttribute('aria-expanded', 'true');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Builds a proxied stream URL with an optional referer parameter.
|
||||
App.videos.buildStreamUrl = function(videoUrl) {
|
||||
let refererParam = '';
|
||||
try {
|
||||
const origin = new URL(videoUrl).origin;
|
||||
refererParam = `&referer=${encodeURIComponent(origin + '/')}`;
|
||||
} catch (err) {
|
||||
refererParam = '';
|
||||
}
|
||||
return `/api/stream?url=${encodeURIComponent(videoUrl)}${refererParam}`;
|
||||
};
|
||||
|
||||
App.videos.downloadVideo = function(video) {
|
||||
if (!video || !video.url) return;
|
||||
const link = document.createElement('a');
|
||||
link.href = App.videos.buildStreamUrl(video.url);
|
||||
const rawName = (video.title || video.id || 'video').toString();
|
||||
const safeName = rawName.replace(/[^a-z0-9]+/gi, '_').replace(/^_+|_+$/g, '').slice(0, 80);
|
||||
link.download = safeName ? `${safeName}.mp4` : 'video.mp4';
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
};
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user