Add support for custom playlists

This commit is contained in:
Omar Roth
2019-08-05 18:49:13 -05:00
parent 1e34a61911
commit be055d9dcb
40 changed files with 1802 additions and 245 deletions

View File

@@ -0,0 +1,56 @@
<% content_for "header" do %>
<title><%= playlist.title %> - Invidious</title>
<link rel="alternate" type="application/rss+xml" title="RSS" href="/feed/playlist/<%= plid %>" />
<% end %>
<div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-5"></div>
<div class="pure-u-1 pure-u-lg-3-5">
<div class="h-box">
<form class="pure-form pure-form-aligned" action="/add_playlist_items" method="get">
<legend><a href="/playlist?list=<%= playlist.id %>"><%= translate(locale, "Editing playlist `x`", %|"#{HTML.escape(playlist.title)}"|) %></a></legend>
<fieldset>
<input class="pure-input-1" type="search" name="q" <% if query %>value="<%= HTML.escape(query) %>"<% else %>placeholder="<%= translate(locale, "Search for videos") %>"<% end %>>
<input type="hidden" name="list" value="<%= plid %>">
</fieldset>
</form>
</div>
</div>
<div class="pure-u-1 pure-u-lg-1-5"></div>
</div>
<script>
var playlist_data = {
csrf_token: '<%= URI.encode_www_form(env.get?("csrf_token").try &.as(String) || "") %>',
}
</script>
<script src="/js/playlist_widget.js"></script>
<div class="pure-g">
<% videos.each_slice(4) do |slice| %>
<% slice.each do |item| %>
<%= rendered "components/item" %>
<% end %>
<% end %>
</div>
<% if query %>
<div class="pure-g h-box">
<div class="pure-u-1 pure-u-lg-1-5">
<% if page > 1 %>
<a href="/add_playlist_items?list=<%= plid %>&q=<%= HTML.escape(query.not_nil!) %>&page=<%= page - 1 %>">
<%= translate(locale, "Previous page") %>
</a>
<% end %>
</div>
<div class="pure-u-1 pure-u-lg-3-5"></div>
<div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
<% if count >= 20 %>
<a href="/add_playlist_items?list=<%= plid %>&q=<%= HTML.escape(query.not_nil!) %>&page=<%= page + 1 %>">
<%= translate(locale, "Next page") %>
</a>
<% end %>
</div>
</div>
<% end %>

View File

@@ -13,7 +13,7 @@
<p><%= translate(locale, "`x` subscribers", number_with_separator(item.subscriber_count)) %></p>
<% if !item.auto_generated %><p><%= translate(locale, "`x` videos", number_with_separator(item.video_count)) %></p><% end %>
<h5><%= item.description_html %></h5>
<% when SearchPlaylist %>
<% when SearchPlaylist, InvidiousPlaylist %>
<% if item.id.starts_with? "RD" %>
<% url = "/mix?list=#{item.id}&continuation=#{URI.parse(item.thumbnail || "/vi/-----------").full_path.split("/")[2]}" %>
<% else %>
@@ -56,6 +56,19 @@
<% if !env.get("preferences").as(Preferences).thin_mode %>
<div class="thumbnail">
<img class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg"/>
<% if plid = env.get?("remove_playlist_items") %>
<form onsubmit="return false" action="/playlist_ajax?action_remove_video=1&set_video_id=<%= item.index %>&playlist_id=<%= plid %>&referer=<%= env.get("current_page") %>" method="post">
<input type="hidden" name="csrf_token" value="<%= URI.encode_www_form(env.get?("csrf_token").try &.as(String) || "") %>">
<p class="watched">
<a onclick="remove_playlist_item(this)" data-index="<%= item.index %>" data-plid="<%= plid %>" href="javascript:void(0)">
<button type="submit" style="all:unset">
<i class="icon ion-md-trash"></i>
</button>
</a>
</p>
</form>
<% end %>
<% if item.responds_to?(:live_now) && item.live_now %>
<p class="length"><i class="icon ion-ios-play-circle"></i> <%= translate(locale, "LIVE") %></p>
<% elsif item.length_seconds != 0 %>
@@ -63,7 +76,7 @@
<% end %>
</div>
<% end %>
<p><%= item.title %></p>
<p><a href="/watch?v=<%= item.id %>"><%= item.title %></a></p>
</a>
<p>
<b>
@@ -103,6 +116,17 @@
</a>
</p>
</form>
<% elsif plid = env.get? "add_playlist_items" %>
<form onsubmit="return false" action="/playlist_ajax?action_add_video=1&video_id=<%= item.id %>&playlist_id=<%= plid %>&referer=<%= env.get("current_page") %>" method="post">
<input type="hidden" name="csrf_token" value="<%= URI.encode_www_form(env.get?("csrf_token").try &.as(String) || "") %>">
<p class="watched">
<a onclick="add_playlist_item(this)" data-id="<%= item.id %>" data-plid="<%= plid %>" href="javascript:void(0)">
<button type="submit" style="all:unset">
<i class="icon ion-md-add"></i>
</button>
</a>
</p>
</form>
<% end %>
<% if item.responds_to?(:live_now) && item.live_now %>

View File

@@ -0,0 +1,39 @@
<% content_for "header" do %>
<title><%= translate(locale, "Create playlist") %> - Invidious</title>
<% end %>
<div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-5"></div>
<div class="pure-u-1 pure-u-lg-3-5">
<div class="h-box">
<form class="pure-form pure-form-aligned" action="/create_playlist?referer=<%= URI.encode_www_form(referer) %>" method="post">
<fieldset>
<legend><%= translate(locale, "Create playlist") %></legend>
<div class="pure-control-group">
<label for="title"><%= translate(locale, "Title") %> :</label>
<input required name="title" type="text" placeholder="<%= translate(locale, "Title") %>">
</div>
<div class="pure-control-group">
<label for="privacy"><%= translate(locale, "Playlist privacy") %> :</label>
<select name="privacy" id="privacy">
<% PlaylistPrivacy.names.each do |option| %>
<option value="<%= option %>" <% if option == "Public" %> selected <% end %>><%= translate(locale, option) %></option>
<% end %>
</select>
</div>
<div class="pure-controls">
<button type="submit" name="action" value="create_playlist" class="pure-button pure-button-primary">
<%= translate(locale, "Create playlist") %>
</button>
</div>
<input type="hidden" name="csrf_token" value="<%= URI.encode_www_form(csrf_token) %>">
</fieldset>
</form>
</div>
</div>
<div class="pure-u-1 pure-u-lg-1-5"></div>
</div>

View File

@@ -0,0 +1,24 @@
<% content_for "header" do %>
<title><%= translate(locale, "Delete playlist") %> - Invidious</title>
<% end %>
<div class="h-box">
<form class="pure-form pure-form-aligned" action="/delete_playlist?list=<%= plid %>&referer=<%= URI.encode_www_form(referer) %>" method="post">
<legend><%= translate(locale, "Delete playlist `x`?", %|"#{HTML.escape(playlist.title)}"|) %></legend>
<div class="pure-g">
<div class="pure-u-1-2">
<button type="submit" name="submit" value="delete_playlist" class="pure-button pure-button-primary">
<%= translate(locale, "Yes") %>
</button>
</div>
<div class="pure-u-1-2">
<a class="pure-button" href="/playlist?list=<%= plid %>">
<%= translate(locale, "No") %>
</a>
</div>
</div>
<input type="hidden" name="csrf_token" value="<%= URI.encode_www_form(csrf_token) %>">
</form>
</div>

View File

@@ -0,0 +1,81 @@
<% content_for "header" do %>
<title><%= playlist.title %> - Invidious</title>
<link rel="alternate" type="application/rss+xml" title="RSS" href="/feed/playlist/<%= plid %>" />
<% end %>
<form class="pure-form" action="/edit_playlist?list=<%= plid %>" method="post">
<div class="pure-g h-box">
<div class="pure-u-2-3">
<h3><input class="pure-input-1" maxlength="150" name="title" type="text" value="<%= playlist.title %>"></h3>
<b>
<%= playlist.author %> |
<%= translate(locale, "`x` videos", "#{playlist.video_count}") %> |
<%= translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %> |
<i class="icon <%= {"ion-md-globe", "ion-ios-unlock", "ion-ios-lock"}[playlist.privacy.value] %>"></i>
<select name="privacy">
<% {"Public", "Unlisted", "Private"}.each do |option| %>
<option value="<%= option %>" <% if option == playlist.privacy.to_s %>selected<% end %>><%= translate(locale, option) %></option>
<% end %>
</select>
</b>
</div>
<div class="pure-u-1-3" style="text-align:right">
<h3>
<div class="pure-g user-field">
<div class="pure-u-1-3">
<a href="javascript:void(0)">
<button type="submit" style="all:unset">
<i class="icon ion-md-save"></i>
</button>
</a>
</div>
<div class="pure-u-1-3"><a href="/delete_playlist?list=<%= plid %>"><i class="icon ion-md-trash"></i></a></div>
<div class="pure-u-1-3"><a href="/feed/playlist/<%= plid %>"><i class="icon ion-logo-rss"></i></a></div>
</div>
</h3>
</div>
</div>
<div class="h-box">
<textarea maxlength="5000" name="description" style="margin-top:10px;max-width:100%;height:20vh" class="pure-input-1"><%= playlist.description %></textarea>
</div>
<input type="hidden" name="csrf_token" value="<%= URI.encode_www_form(csrf_token) %>">
</form>
<% if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email %>
<div class="h-box" style="text-align:right">
<h3>
<a href="/add_playlist_items?list=<%= plid %>"><i class="icon ion-md-add"></i></a>
</h3>
</div>
<% end %>
<div class="h-box">
<hr>
</div>
<div class="pure-g">
<% videos.each_slice(4) do |slice| %>
<% slice.each do |item| %>
<%= rendered "components/item" %>
<% end %>
<% end %>
</div>
<div class="pure-g h-box">
<div class="pure-u-1 pure-u-lg-1-5">
<% if page > 1 %>
<a href="/playlist?list=<%= playlist.id %>&page=<%= page - 1 %>">
<%= translate(locale, "Previous page") %>
</a>
<% end %>
</div>
<div class="pure-u-1 pure-u-lg-3-5"></div>
<div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
<% if videos.size == 100 %>
<a href="/playlist?list=<%= playlist.id %>&page=<%= page + 1 %>">
<%= translate(locale, "Next page") %>
</a>
<% end %>
</div>
</div>

View File

@@ -29,6 +29,7 @@
<script>
var video_data = {
id: '<%= video.id %>',
index: '<%= continuation %>',
plid: '<%= plid %>',
length_seconds: '<%= video.length_seconds.to_f %>',
video_series: <%= video_series.to_json %>,

View File

@@ -6,36 +6,77 @@
<div class="pure-g h-box">
<div class="pure-u-2-3">
<h3><%= playlist.title %></h3>
<% if playlist.is_a? InvidiousPlaylist %>
<b>
<% if playlist.author == user.try &.email %>
<a href="/view_all_playlists"><%= playlist.author %></a> |
<% else %>
<%= playlist.author %> |
<% end %>
<%= translate(locale, "`x` videos", "#{playlist.video_count}") %> |
<%= translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %> |
<% case playlist.as(InvidiousPlaylist).privacy when %>
<% when PlaylistPrivacy::Public %>
<i class="icon ion-md-globe"></i> <%= translate(locale, "Public") %>
<% when PlaylistPrivacy::Unlisted %>
<i class="icon ion-ios-unlock"></i> <%= translate(locale, "Unlisted") %>
<% when PlaylistPrivacy::Private %>
<i class="icon ion-ios-lock"></i> <%= translate(locale, "Private") %>
<% end %>
</b>
<% else %>
<b>
<a href="/channel/<%= playlist.ucid %>"><%= playlist.author %></a> |
<%= translate(locale, "`x` videos", "#{playlist.video_count}") %> |
<%= translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %>
</b>
<% end %>
<% if !playlist.is_a? InvidiousPlaylist %>
<div class="pure-u-2-3">
<a href="https://www.youtube.com/playlist?list=<%= playlist.id %>">
<%= translate(locale, "View playlist on YouTube") %>
</a>
</div>
<% end %>
</div>
<div class="pure-u-1-3" style="text-align:right">
<h3>
<a href="/feed/playlist/<%= plid %>"><i class="icon ion-logo-rss"></i></a>
<div class="pure-g user-field">
<% if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email %>
<div class="pure-u-1-3"><a href="/edit_playlist?list=<%= plid %>"><i class="icon ion-md-create"></i></a></div>
<div class="pure-u-1-3"><a href="/delete_playlist?list=<%= plid %>"><i class="icon ion-md-trash"></i></a></div>
<% end %>
<div class="pure-u-1-3"><a href="/feed/playlist/<%= plid %>"><i class="icon ion-logo-rss"></i></a></div>
</div>
</h3>
</div>
</div>
<div class="pure-g h-box">
<div class="pure-u-1-3">
<a href="https://www.youtube.com/playlist?list=<%= playlist.id %>">
<%= translate(locale, "View playlist on YouTube") %>
</a>
<div class="pure-u-1 pure-md-1-3">
<a href="/channel/<%= playlist.ucid %>">
<b><%= playlist.author %></b>
</a>
</div>
</div>
<div class="pure-u-1-2"></div>
</div>
<div class="h-box">
<p><%= playlist.description_html %></p>
</div>
<% if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email %>
<div class="h-box" style="text-align:right">
<h3>
<a href="/add_playlist_items?list=<%= plid %>"><i class="icon ion-md-add"></i></a>
</h3>
</div>
<% end %>
<div class="h-box">
<hr>
</div>
<% if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email %>
<script>
var playlist_data = {
csrf_token: '<%= URI.encode_www_form(env.get?("csrf_token").try &.as(String) || "") %>',
}
</script>
<script src="/js/playlist_widget.js"></script>
<% end %>
<div class="pure-g">
<% videos.each_slice(4) do |slice| %>
<% slice.each do |item| %>

View File

@@ -261,6 +261,10 @@ function update_value(element) {
<a href="/token_manager"><%= translate(locale, "Manage tokens") %></a>
</div>
<div class="pure-control-group">
<a href="/view_all_playlists"><%= translate(locale, "View all playlists") %></a>
</div>
<div class="pure-control-group">
<a href="/feed/history"><%= translate(locale, "Watch history") %></a>
</div>

View File

@@ -0,0 +1,22 @@
<% content_for "header" do %>
<title><%= translate(locale, "Playlists") %> - Invidious</title>
<% end %>
<div class="pure-g h-box">
<div class="pure-u-2-3">
<h3><%= translate(locale, "`x` playlists", %(<span id="count">#{items.size}</span>)) %></h3>
</div>
<div class="pure-u-1-3" style="text-align:right">
<h3>
<a href="/create_playlist?referer=<%= URI.encode_www_form(referer) %>"><%= translate(locale, "Create playlist") %></a>
</h3>
</div>
</div>
<div class="pure-g">
<% items.each_slice(4) do |slice| %>
<% slice.each do |item| %>
<%= rendered "components/item" %>
<% end %>
<% end %>
</div>

View File

@@ -29,6 +29,7 @@
<script>
var video_data = {
id: '<%= video.id %>',
index: '<%= continuation %>',
plid: '<%= plid %>',
length_seconds: <%= video.length_seconds.to_f %>,
play_next: <%= !rvs.empty? && !plid && params.continue %>,