diff --git a/webui/static/video/video-detail.js b/webui/static/video/video-detail.js
index b5858a70..30c614c8 100644
--- a/webui/static/video/video-detail.js
+++ b/webui/static/video/video-detail.js
@@ -164,13 +164,19 @@
var meta = [];
if (d.source === 'youtube') {
- meta.push('YouTube');
+ var isPl = d.kind === 'playlist';
+ meta.push('' + (isPl ? 'Playlist' : 'YouTube') + '');
var yc = window.VideoYoutube;
- var subs = yc && yc.compactCount(d.subscriber_count); if (subs) meta.push('' + subs + ' subscribers');
- // NB: no "N videos" — YouTube doesn't expose a reliable total, and what we
- // fetch is just the recent page (the cap), so showing it would mislead.
- var views = yc && yc.compactCount(d.view_count); if (views) meta.push('' + views + ' views');
- if (d.handle) meta.push('' + esc(d.handle) + '');
+ if (isPl) {
+ // A playlist IS a known, fixed list, so its count is real (unlike a
+ // channel's total). Owner shows as a genre tag below.
+ if (d.video_count != null) meta.push('' + esc(d.video_count) + ' videos');
+ } else {
+ var subs = yc && yc.compactCount(d.subscriber_count); if (subs) meta.push('' + subs + ' subscribers');
+ // NB: no "N videos" for a CHANNEL — YouTube doesn't expose a reliable total.
+ var views = yc && yc.compactCount(d.view_count); if (views) meta.push('' + views + ' views');
+ if (d.handle) meta.push('' + esc(d.handle) + '');
+ }
var mm = q('[data-vd-meta]'); if (mm) mm.innerHTML = meta.join('');
renderActions(d);
var ll = q('[data-vd-links]'); if (ll) ll.innerHTML = '';
@@ -869,6 +875,7 @@
function renderViewToggle() {
var host = q('[data-vd-view-toggle]');
if (!host) return;
+ if (data && data.kind === 'playlist') { host.innerHTML = ''; return; } // flat list — no view toggle
host.innerHTML = VIEWS.map(function (v) {
return '';
}
+ // A pasted-playlist result chip (mirrors searchCard): cover, title, owner +
+ // count, and an Add-to-watchlist toggle (data-vyt-follow-playlist).
+ function playlistCard(pl, following) {
+ var cover = pl.thumbnail_url
+ ? '
▤\'">'
+ : '▤';
+ var sub = [];
+ if (pl.channel_title) sub.push(esc(pl.channel_title));
+ if (pl.video_count != null) sub.push(esc(pl.video_count) + ' videos');
+ return '
' +
+ '
' + cover +
+ '
' +
+ 'YouTube playlist' +
+ '' + esc(pl.title) + '' +
+ (sub.length ? '' + sub.join(' · ') + '' : '') +
+ '
' +
+ '
' + (following ? '✓ Following' : '+ Follow') + '' +
+ '
';
+ }
+
function post(url, body) {
return fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body || {}) }).then(function (r) { return r.ok ? r.json() : null; });
@@ -114,6 +145,14 @@
{ channel: { youtube_id: ch.youtube_id, title: ch.title, avatar_url: ch.avatar_url } });
}
function unfollow(youtubeId) { return post('/api/video/youtube/unfollow', { youtube_id: youtubeId }); }
+ function followPlaylist(pl) {
+ var p = pl || {};
+ return post('/api/video/youtube/playlist/follow', { playlist: {
+ playlist_id: p.playlist_id, title: p.title, thumbnail_url: p.thumbnail_url } });
+ }
+ function unfollowPlaylist(playlistId) {
+ return post('/api/video/youtube/playlist/unfollow', { playlist_id: playlistId });
+ }
function removeWish(scope, sourceId) {
return post('/api/video/youtube/wishlist/remove', { scope: scope, source_id: sourceId });
}
@@ -161,10 +200,11 @@
}
window.VideoYoutube = {
- esc: esc, isChannelRef: isChannelRef, fmtDate: fmtDate, avatar: avatar, img: img,
+ esc: esc, isChannelRef: isChannelRef, isPlaylistRef: isPlaylistRef, fmtDate: fmtDate, avatar: avatar, img: img,
fmtDuration: fmtDuration, compactCount: compactCount,
- videoCard: videoCard, searchCard: searchCard, followBtn: followBtn,
- follow: follow, unfollow: unfollow, removeWish: removeWish, addVideos: addVideos, resolve: resolve,
+ videoCard: videoCard, searchCard: searchCard, playlistCard: playlistCard, followBtn: followBtn,
+ follow: follow, unfollow: unfollow, followPlaylist: followPlaylist, unfollowPlaylist: unfollowPlaylist,
+ removeWish: removeWish, addVideos: addVideos, resolve: resolve,
searchChannels: searchChannels, channelResultCard: channelResultCard,
};
})();