Minor formatting changes

This commit is contained in:
Omar Roth 2019-05-01 20:03:39 -05:00
parent 22b9bbe702
commit 1a9360ca75
No known key found for this signature in database
GPG Key ID: B8254FB7EC3D37F2
31 changed files with 1406 additions and 1337 deletions

View File

@ -1,52 +1,52 @@
function toggle_parent(target) { function toggle_parent(target) {
body = target.parentNode.parentNode.children[1]; body = target.parentNode.parentNode.children[1];
if (body.style.display === null || body.style.display === "") { if (body.style.display === null || body.style.display === "") {
target.innerHTML = "[ + ]"; target.innerHTML = "[ + ]";
body.style.display = "none"; body.style.display = "none";
} else { } else {
target.innerHTML = "[ - ]"; target.innerHTML = "[ - ]";
body.style.display = ""; body.style.display = "";
} }
} }
function toggle_comments(target) { function toggle_comments(target) {
body = target.parentNode.parentNode.parentNode.children[1]; body = target.parentNode.parentNode.parentNode.children[1];
if (body.style.display === null || body.style.display === "") { if (body.style.display === null || body.style.display === "") {
target.innerHTML = "[ + ]"; target.innerHTML = "[ + ]";
body.style.display = "none"; body.style.display = "none";
} else { } else {
target.innerHTML = "[ - ]"; target.innerHTML = "[ - ]";
body.style.display = ""; body.style.display = "";
} }
} }
function swap_comments(source) { function swap_comments(source) {
if (source == "youtube") { if (source == "youtube") {
get_youtube_comments(); get_youtube_comments();
} else if (source == "reddit") { } else if (source == "reddit") {
get_reddit_comments(); get_reddit_comments();
} }
} }
String.prototype.supplant = function(o) { String.prototype.supplant = function (o) {
return this.replace(/{([^{}]*)}/g, function(a, b) { return this.replace(/{([^{}]*)}/g, function (a, b) {
var r = o[b]; var r = o[b];
return typeof r === "string" || typeof r === "number" ? r : a; return typeof r === "string" || typeof r === "number" ? r : a;
}); });
}; };
function show_youtube_replies(target, inner_text, sub_text) { function show_youtube_replies(target, inner_text, sub_text) {
body = target.parentNode.parentNode.children[1]; body = target.parentNode.parentNode.children[1];
body.style.display = ""; body.style.display = "";
target.innerHTML = inner_text; target.innerHTML = inner_text;
target.setAttribute("onclick", "hide_youtube_replies(this, \'" + inner_text + "\', \'" + sub_text + "\')"); target.setAttribute("onclick", "hide_youtube_replies(this, \'" + inner_text + "\', \'" + sub_text + "\')");
} }
function hide_youtube_replies(target, inner_text, sub_text) { function hide_youtube_replies(target, inner_text, sub_text) {
body = target.parentNode.parentNode.children[1]; body = target.parentNode.parentNode.children[1];
body.style.display = "none"; body.style.display = "none";
target.innerHTML = sub_text; target.innerHTML = sub_text;
target.setAttribute("onclick", "show_youtube_replies(this, \'" + inner_text + "\', \'" + sub_text + "\')"); target.setAttribute("onclick", "show_youtube_replies(this, \'" + inner_text + "\', \'" + sub_text + "\')");
} }

View File

@ -309,7 +309,7 @@ def template_youtube_comments(comments, locale, thin_mode)
html += <<-END_HTML html += <<-END_HTML
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-4-24 pure-u-md-2-24"> <div class="pure-u-4-24 pure-u-md-2-24">
<img style="width:90%; padding-right:1em; padding-top:1em;" src="#{author_thumbnail}"> <img style="width:90%;padding-right:1em;padding-top:1em" src="#{author_thumbnail}">
</div> </div>
<div class="pure-u-20-24 pure-u-md-22-24"> <div class="pure-u-20-24 pure-u-md-22-24">
<p> <p>

View File

@ -105,7 +105,7 @@ def template_mix(mix)
</div> </div>
<p style="width:100%">#{video["title"]}</p> <p style="width:100%">#{video["title"]}</p>
<p> <p>
<b style="width: 100%">#{video["author"]}</b> <b style="width:100%">#{video["author"]}</b>
</p> </p>
</a> </a>
</li> </li>

View File

@ -248,7 +248,7 @@ def template_playlist(playlist)
</div> </div>
<p style="width:100%">#{video["title"]}</p> <p style="width:100%">#{video["title"]}</p>
<p> <p>
<b style="width: 100%">#{video["author"]}</b> <b style="width:100%">#{video["author"]}</b>
</p> </p>
</a> </a>
</li> </li>

View File

@ -3,76 +3,76 @@
<% end %> <% end %>
<% if env.get? "access_token" %> <% if env.get? "access_token" %>
<div class="pure-g h-box"> <div class="pure-g h-box">
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<h3> <h3>
<%= translate(locale, "Token") %> <%= translate(locale, "Token") %>
</h3> </h3>
</div>
<div class="pure-u-1-3" style="text-align:center">
<h3>
<a href="/token_manager"><%= translate(locale, "Token manager") %></a>
</h3>
</div>
<div class="pure-u-1-3" style="text-align:right">
<h3>
<a href="/preferences"><%= translate(locale, "Preferences") %></a>
</h3>
</div>
</div> </div>
<div class="pure-u-1-3" style="text-align:center">
<h3>
<a href="/token_manager"><%= translate(locale, "Token manager") %></a>
</h3>
</div>
<div class="pure-u-1-3" style="text-align:right">
<h3>
<a href="/preferences"><%= translate(locale, "Preferences") %></a>
</h3>
</div>
</div>
<div class="h-box"> <div class="h-box">
<h4 style="padding-left:0.5em"> <h4 style="padding-left:0.5em">
<code><%= env.get "access_token" %></code> <code><%= env.get "access_token" %></code>
</h4> </h4>
</div> </div>
<% else %> <% else %>
<div class="h-box"> <div class="h-box">
<form class="pure-form pure-form-aligned" action="/authorize_token" method="post"> <form class="pure-form pure-form-aligned" action="/authorize_token" method="post">
<% if callback_url %> <% if callback_url %>
<legend><%= translate(locale, "Authorize token for `x`?", "#{callback_url.scheme}://#{callback_url.host}") %></legend> <legend><%= translate(locale, "Authorize token for `x`?", "#{callback_url.scheme}://#{callback_url.host}") %></legend>
<% else %> <% else %>
<legend><%= translate(locale, "Authorize token?") %></legend> <legend><%= translate(locale, "Authorize token?") %></legend>
<% end %> <% end %>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1"> <div class="pure-u-1">
<ul> <ul>
<% scopes.each do |scope| %> <% scopes.each do |scope| %>
<li><%= scope %></li> <li><%= scope %></li>
<% end %> <% end %>
</ul> </ul>
</div>
</div> </div>
</div>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1-2"> <div class="pure-u-1-2">
<button type="submit" name="submit" value="clear_watch_history" class="pure-button pure-button-primary"> <button type="submit" name="submit" value="clear_watch_history" class="pure-button pure-button-primary">
<%= translate(locale, "Yes") %> <%= translate(locale, "Yes") %>
</button> </button>
</div> </div>
<div class="pure-u-1-2"> <div class="pure-u-1-2">
<% if callback_url %> <% if callback_url %>
<a class="pure-button" href="<%= callback_url %>"> <a class="pure-button" href="<%= callback_url %>">
<% else %> <% else %>
<a class="pure-button" href="/"> <a class="pure-button" href="/">
<% end %> <% end %>
<%= translate(locale, "No") %> <%= translate(locale, "No") %>
</a> </a>
</div>
</div> </div>
</div>
<% scopes.each_with_index do |scope, i| %> <% scopes.each_with_index do |scope, i| %>
<input type="hidden" name="scopes[<%= i %>]" value="<%= scope %>"> <input type="hidden" name="scopes[<%= i %>]" value="<%= scope %>">
<% end %> <% end %>
<% if callback_url %> <% if callback_url %>
<input type="hidden" name="callbackUrl" value="<%= callback_url %>"> <input type="hidden" name="callbackUrl" value="<%= callback_url %>">
<% end %> <% end %>
<% if expire %> <% if expire %>
<input type="hidden" name="expire" value="<%= expire %>"> <input type="hidden" name="expire" value="<%= expire %>">
<% end %> <% end %>
<input type="hidden" name="csrf_token" value="<%= URI.escape(csrf_token) %>"> <input type="hidden" name="csrf_token" value="<%= URI.escape(csrf_token) %>">
</form> </form>
</div> </div>
<% end %> <% end %>

View File

@ -7,7 +7,7 @@
<div class="pure-u-2-3"> <div class="pure-u-2-3">
<h3><%= author %></h3> <h3><%= author %></h3>
</div> </div>
<div class="pure-u-1-3" style="text-align:right;"> <div class="pure-u-1-3" style="text-align:right">
<h3> <h3>
<a href="/feed/channel/<%= ucid %>"><i class="icon ion-logo-rss"></i></a> <a href="/feed/channel/<%= ucid %>"><i class="icon ion-logo-rss"></i></a>
</h3> </h3>
@ -15,41 +15,40 @@
</div> </div>
<div class="h-box"> <div class="h-box">
<% sub_count_text = number_to_short_text(sub_count) %> <% sub_count_text = number_to_short_text(sub_count) %>
<%= rendered "components/subscribe_widget" %> <%= rendered "components/subscribe_widget" %>
</div> </div>
<div class="pure-g h-box"> <div class="pure-g h-box">
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<a href="https://www.youtube.com/channel/<%= ucid %>"><%= translate(locale, "View channel on YouTube") %></a> <a href="https://www.youtube.com/channel/<%= ucid %>"><%= translate(locale, "View channel on YouTube") %></a>
<% if !auto_generated %> <% if !auto_generated %>
<div class="pure-u-1 pure-md-1-3">
<b><%= translate(locale, "Videos") %></b>
</div>
<% end %>
<div class="pure-u-1 pure-md-1-3">
<% if auto_generated %>
<b><%= translate(locale, "Playlists") %></b>
<% else %>
<a href="/channel/<%= ucid %>/playlists"><%= translate(locale, "Playlists") %></a>
<% end %>
</div>
</div>
<div class="pure-u-1-3">
</div>
<div class="pure-u-1-3">
<div class="pure-g" style="text-align:right;">
<% sort_options.each do |sort| %>
<div class="pure-u-1 pure-md-1-3"> <div class="pure-u-1 pure-md-1-3">
<% if sort_by == sort %> <b><%= translate(locale, "Videos") %></b>
<b><%= translate(locale, sort) %></b>
<% else %>
<a href="/channel/<%= ucid %>?page=<%= page %>&sort_by=<%= sort %>">
<%= translate(locale, sort) %>
</a>
<% end %>
</div> </div>
<% end %> <% end %>
<div class="pure-u-1 pure-md-1-3">
<% if auto_generated %>
<b><%= translate(locale, "Playlists") %></b>
<% else %>
<a href="/channel/<%= ucid %>/playlists"><%= translate(locale, "Playlists") %></a>
<% end %>
</div>
</div>
<div class="pure-u-1-3"></div>
<div class="pure-u-1-3">
<div class="pure-g" style="text-align:right">
<% sort_options.each do |sort| %>
<div class="pure-u-1 pure-md-1-3">
<% if sort_by == sort %>
<b><%= translate(locale, sort) %></b>
<% else %>
<a href="/channel/<%= ucid %>?page=<%= page %>&sort_by=<%= sort %>">
<%= translate(locale, sort) %>
</a>
<% end %>
</div>
<% end %>
</div> </div>
</div> </div>
</div> </div>
@ -59,28 +58,28 @@
</div> </div>
<div class="pure-g"> <div class="pure-g">
<% items.each_slice(4) do |slice| %> <% items.each_slice(4) do |slice| %>
<% slice.each do |item| %> <% slice.each do |item| %>
<%= rendered "components/item" %> <%= rendered "components/item" %>
<% end %>
<% end %> <% end %>
<% end %>
</div> </div>
<div class="pure-g h-box"> <div class="pure-g h-box">
<div class="pure-u-1 pure-u-lg-1-5"> <div class="pure-u-1 pure-u-lg-1-5">
<% if page >= 2 %> <% if page >= 2 %>
<a href="/channel/<%= ucid %>?page=<%= page - 1 %><% if sort_by != "newest" %>&sort_by=<%= sort_by %><% end %>"> <a href="/channel/<%= ucid %>?page=<%= page - 1 %><% if sort_by != "newest" %>&sort_by=<%= sort_by %><% end %>">
<%= translate(locale, "Previous page") %> <%= translate(locale, "Previous page") %>
</a> </a>
<% end %> <% end %>
</div> </div>
<div class="pure-u-1 pure-u-lg-3-5"></div> <div class="pure-u-1 pure-u-lg-3-5"></div>
<div style="text-align:right" class="pure-u-1 pure-u-lg-1-5"> <div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
<% if count == 60 %> <% if count == 60 %>
<a href="/channel/<%= ucid %>?page=<%= page + 1 %><% if sort_by != "newest" %>&sort_by=<%= sort_by %><% end %>"> <a href="/channel/<%= ucid %>?page=<%= page + 1 %><% if sort_by != "newest" %>&sort_by=<%= sort_by %><% end %>">
<%= translate(locale, "Next page") %> <%= translate(locale, "Next page") %>
</a> </a>
<% end %> <% end %>
</div> </div>
</div> </div>

View File

@ -4,16 +4,16 @@
<div class="pure-g"> <div class="pure-g">
<% feed_menu = config.feed_menu.dup %> <% feed_menu = config.feed_menu.dup %>
<% if !env.get?("user") %> <% if !env.get?("user") %>
<% feed_menu.reject! {|feed| feed == "Subscriptions"} %> <% feed_menu.reject! {|feed| feed == "Subscriptions"} %>
<% end %> <% end %>
<% feed_menu.each do |feed| %> <% feed_menu.each do |feed| %>
<div class="pure-u-1-2 pure-u-md-1-<%= feed_menu.size %>"> <div class="pure-u-1-2 pure-u-md-1-<%= feed_menu.size %>">
<a href="/feed/<%= feed.downcase %>" style="text-align:center;" class="pure-menu-heading"> <a href="/feed/<%= feed.downcase %>" class="pure-menu-heading" style="text-align:center">
<%= translate(locale, feed) %> <%= translate(locale, feed) %>
</a> </a>
</div> </div>
<% end %> <% end %>
</div> </div>
</div> </div>
<div class="pure-u-1 pure-u-md-1-4"></div> <div class="pure-u-1 pure-u-md-1-4"></div>
</div> </div>

View File

@ -1,133 +1,138 @@
<div class="pure-u-1 pure-u-md-1-4"> <div class="pure-u-1 pure-u-md-1-4">
<div class="h-box"> <div class="h-box">
<% case item when %> <% case item when %>
<% when SearchChannel %> <% when SearchChannel %>
<a style="width:100%;" href="/channel/<%= item.ucid %>"> <a style="width:100%" href="/channel/<%= item.ucid %>">
<% if env.get("preferences").as(Preferences).thin_mode %> <% if !env.get("preferences").as(Preferences).thin_mode %>
<center>
<img style="width:56.25%" src="/ggpht<%= URI.parse(item.author_thumbnail).full_path %>"/>
</center>
<% end %>
<p><%= item.author %></p>
</a>
<p><%= translate(locale, "`x` subscribers", number_with_separator(item.subscriber_count)) %></p>
<p><%= translate(locale, "`x` videos", number_with_separator(item.video_count)) %></p>
<h5><%= item.description_html %></h5>
<% when SearchPlaylist %>
<% if item.id.starts_with? "RD" %>
<% url = "/mix?list=#{item.id}&continuation=#{item.thumbnail_id}" %>
<% else %> <% else %>
<center> <% url = "/playlist?list=#{item.id}" %>
<img style="width:56.25%;" src="/ggpht<%= URI.parse(item.author_thumbnail).full_path %>"/>
</center>
<% end %> <% end %>
<p><%= item.author %></p>
</a> <a style="width:100%" href="<%= url %>">
<p><%= translate(locale, "`x` subscribers", number_with_separator(item.subscriber_count)) %></p> <% if !env.get("preferences").as(Preferences).thin_mode %>
<p><%= translate(locale, "`x` videos", number_with_separator(item.video_count)) %></p> <div class="thumbnail">
<h5><%= item.description_html %></h5> <img class="thumbnail" src="/vi/<%= item.thumbnail_id %>/mqdefault.jpg"/>
<% when SearchPlaylist %> <p class="length"><%= number_with_separator(item.video_count) %> videos</p>
<% if item.id.starts_with? "RD" %> </div>
<% url = "/mix?list=#{item.id}&continuation=#{item.thumbnail_id}" %> <% end %>
<p><%= item.title %></p>
</a>
<p>
<b>
<a style="width:100%" href="/channel/<%= item.ucid %>"><%= item.author %></a>
</b>
</p>
<% when MixVideo %>
<a style="width:100%" href="/watch?v=<%= item.id %>&list=<%= item.mixes[0] %>">
<% if !env.get("preferences").as(Preferences).thin_mode %>
<div class="thumbnail">
<img class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg"/>
<% if item.length_seconds != 0 %>
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
<% end %>
</div>
<% end %>
<p><%= item.title %></p>
</a>
<p>
<b>
<a style="width:100%" href="/channel/<%= item.ucid %>"><%= item.author %></a>
</b>
</p>
<% when PlaylistVideo %>
<a style="width:100%" href="/watch?v=<%= item.id %>&list=<%= item.playlists[0] %>">
<% if !env.get("preferences").as(Preferences).thin_mode %>
<div class="thumbnail">
<img class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg"/>
<% 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 %>
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
<% end %>
</div>
<% end %>
<p><%= item.title %></p>
</a>
<p>
<b>
<a style="width:100%" href="/channel/<%= item.ucid %>"><%= item.author %></a>
</b>
</p>
<h5 class="pure-g">
<% if item.responds_to?(:premiere_timestamp) && item.premiere_timestamp && item.premiere_timestamp.not_nil! > Time.now %>
<%= translate(locale, "Premieres in `x`", recode_date((item.premiere_timestamp.as(Time) - Time.now).ago, locale)) %></h5>
<% elsif Time.now - item.published > 1.minute %>
<div class="pure-u-2-3"><%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %></div>
<% else %>
<div class="pure-u-2-3"></div>
<% end %>
<div class="pure-u-1-3" style="text-align:right">
<%= item.responds_to?(:views) ? translate(locale, "`x` views", number_to_short_text(item.views)) : "" %>
</div>
</h5>
<% else %> <% else %>
<% url = "/playlist?list=#{item.id}" %> <% if !env.get("preferences").as(Preferences).thin_mode %>
<a style="width:100%" href="/watch?v=<%= item.id %>">
<div class="thumbnail">
<img class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg"/>
<% if env.get? "show_watched" %>
<form onsubmit="return false" action="/watch_ajax?action_mark_watched=1&id=<%= item.id %>&referer=<%= env.get("current_page") %>" method="post">
<input type="hidden" name="csrf_token" value="<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>">
<p class="watched">
<a onclick="mark_watched(this)" data-id="<%= item.id %>" href="#">
<button type="submit" style="all:unset">
<i onmouseenter='this.setAttribute("class", "icon ion-ios-eye-off")'
onmouseleave='this.setAttribute("class", "icon ion-ios-eye")'
class="icon ion-ios-eye">
</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 %>
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
<% end %>
</div>
</a>
<% end %>
<p><a href="/watch?v=<%= item.id %>"><%= item.title %></a></p>
<p>
<b>
<a style="width:100%" href="/channel/<%= item.ucid %>"><%= item.author %></a>
</b>
</p>
<h5 class="pure-g">
<% if item.responds_to?(:premiere_timestamp) && item.premiere_timestamp && item.premiere_timestamp.not_nil! > Time.now %>
<%= translate(locale, "Premieres in `x`", recode_date((item.premiere_timestamp.as(Time) - Time.now).ago, locale)) %></h5>
<% elsif Time.now - item.published > 1.minute %>
<div class="pure-u-2-3"><%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %></div>
<% else %>
<div class="pure-u-2-3"></div>
<% end %>
<div class="pure-u-1-3" style="text-align:right">
<%= item.responds_to?(:views) ? translate(locale, "`x` views", number_to_short_text(item.views)) : "" %>
</div>
</h5>
<% end %> <% end %>
<a style="width:100%;" href="<%= url %>">
<% if env.get("preferences").as(Preferences).thin_mode %>
<% else %>
<div class="thumbnail">
<img class="thumbnail" src="/vi/<%= item.thumbnail_id %>/mqdefault.jpg"/>
<p class="length"><%= number_with_separator(item.video_count) %> videos</p>
</div>
<% end %>
<p><%= item.title %></p>
</a>
<p>
<b><a style="width:100%;" href="/channel/<%= item.ucid %>"><%= item.author %></a></b>
</p>
<% when MixVideo %>
<a style="width:100%;" href="/watch?v=<%= item.id %>&list=<%= item.mixes[0] %>">
<% if env.get("preferences").as(Preferences).thin_mode %>
<% else %>
<div class="thumbnail">
<img class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg"/>
<% if item.length_seconds != 0 %>
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
<% end %>
</div>
<% end %>
<p><%= item.title %></p>
</a>
<p>
<b><a style="width:100%;" href="/channel/<%= item.ucid %>"><%= item.author %></a></b>
</p>
<% when PlaylistVideo %>
<a style="width:100%;" href="/watch?v=<%= item.id %>&list=<%= item.playlists[0] %>">
<% if env.get("preferences").as(Preferences).thin_mode %>
<% else %>
<div class="thumbnail">
<img class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg"/>
<% 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 %>
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
<% end %>
</div>
<% end %>
<p><%= item.title %></p>
</a>
<p>
<b><a style="width:100%;" href="/channel/<%= item.ucid %>"><%= item.author %></a></b>
</p>
<h5 class="pure-g">
<% if item.responds_to?(:premiere_timestamp) && item.premiere_timestamp && item.premiere_timestamp.not_nil! > Time.now %>
<%= translate(locale, "Premieres in `x`", recode_date((item.premiere_timestamp.as(Time) - Time.now).ago, locale)) %></h5>
<% elsif Time.now - item.published > 1.minute %>
<div class="pure-u-2-3"><%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %></div>
<% else %>
<div class="pure-u-2-3"></div>
<% end %>
<div class="pure-u-1-3" style="text-align: right">
<%= item.responds_to?(:views) ? translate(locale, "`x` views", number_to_short_text(item.views)) : "" %>
</div>
</h5>
<% else %>
<% if env.get("preferences").as(Preferences).thin_mode %>
<% else %>
<a style="width:100%;" href="/watch?v=<%= item.id %>">
<div class="thumbnail">
<img class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg"/>
<% if env.get? "show_watched" %>
<form onsubmit="return false;" action="/watch_ajax?action_mark_watched=1&id=<%= item.id %>&referer=<%= env.get("current_page") %>" method="post">
<input type="hidden" name="csrf_token" value="<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>">
<p class="watched">
<a onclick="mark_watched(this)" data-id="<%= item.id %>" href="#">
<button type="submit" style="all:unset">
<i onmouseenter='this.setAttribute("class", "icon ion-ios-eye-off")'
onmouseleave='this.setAttribute("class", "icon ion-ios-eye")'
class="icon ion-ios-eye">
</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 %>
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
<% end %>
</div>
</a>
<% end %>
<p><a href="/watch?v=<%= item.id %>"><%= item.title %></a></p>
<p>
<b><a style="width:100%;" href="/channel/<%= item.ucid %>"><%= item.author %></a></b>
</p>
<h5 class="pure-g">
<% if item.responds_to?(:premiere_timestamp) && item.premiere_timestamp && item.premiere_timestamp.not_nil! > Time.now %>
<%= translate(locale, "Premieres in `x`", recode_date((item.premiere_timestamp.as(Time) - Time.now).ago, locale)) %></h5>
<% elsif Time.now - item.published > 1.minute %>
<div class="pure-u-2-3"><%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %></div>
<% else %>
<div class="pure-u-2-3"></div>
<% end %>
<div class="pure-u-1-3" style="text-align: right">
<%= item.responds_to?(:views) ? translate(locale, "`x` views", number_to_short_text(item.views)) : "" %>
</div>
</h5>
<% end %>
</div> </div>
</div> </div>

View File

@ -25,161 +25,163 @@
<source src="/latest_version?id=<%= video.id %>&itag=<%= fmt["itag"] %><% if params.local %>&local=true<% end %>" type='<%= fmt["type"] %>' label="<%= fmt["label"] %>" selected="<%= i == 0 ? true : false %>"> <source src="/latest_version?id=<%= video.id %>&itag=<%= fmt["itag"] %><% if params.local %>&local=true<% end %>" type='<%= fmt["type"] %>' label="<%= fmt["label"] %>" selected="<%= i == 0 ? true : false %>">
<% end %> <% end %>
<% end %> <% end %>
<% end %> <% end %>
<% preferred_captions.each_with_index do |caption, i| %> <% preferred_captions.each_with_index do |caption, i| %>
<track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name.simpleText %>&hl=<%= env.get("preferences").as(Preferences).locale %>" <track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name.simpleText %>&hl=<%= env.get("preferences").as(Preferences).locale %>"
label="<%= caption.name.simpleText %>" <% if i == 0 %>default<% end %>> label="<%= caption.name.simpleText %>" <% if i == 0 %>default<% end %>>
<% end %> <% end %>
<% captions.each do |caption| %> <% captions.each do |caption| %>
<track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name.simpleText %>&hl=<%= env.get("preferences").as(Preferences).locale %>" <track kind="captions" src="/api/v1/captions/<%= video.id %>?label=<%= caption.name.simpleText %>&hl=<%= env.get("preferences").as(Preferences).locale %>"
label="<%= caption.name.simpleText %>"> label="<%= caption.name.simpleText %>">
<% end %> <% end %>
<% end %> <% end %>
</video> </video>
<script> <script>
var options = { var options = {
<% if aspect_ratio %> <% if aspect_ratio %>
aspectRatio: "<%= aspect_ratio %>", aspectRatio: "<%= aspect_ratio %>",
<% end %> <% end %>
preload: "auto", preload: "auto",
playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0], playbackRates: [0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0],
controlBar: { controlBar: {
children: [ children: [
"playToggle", "playToggle",
"volumePanel", "volumePanel",
"currentTimeDisplay", "currentTimeDisplay",
"timeDivider", "timeDivider",
"durationDisplay", "durationDisplay",
"progressControl", "progressControl",
"remainingTimeDisplay", "remainingTimeDisplay",
"captionsButton", "captionsButton",
"qualitySelector", "qualitySelector",
"playbackRateMenuButton", "playbackRateMenuButton",
"fullscreenToggle" "fullscreenToggle"
] ]
} }
}; };
var shareOptions = { var shareOptions = {
socials: ["fbFeed", "tw", "reddit", "email"], socials: ["fbFeed", "tw", "reddit", "email"],
url: window.location.href, url: window.location.href,
title: "<%= video.title.dump_unquoted %>", title: "<%= video.title.dump_unquoted %>",
description: "<%= description %>", description: "<%= description %>",
image: "<%= thumbnail %>", image: "<%= thumbnail %>",
embedCode: "<iframe id='ivplayer' type='text/html' width='640' height='360' \ embedCode: "<iframe id='ivplayer' type='text/html' width='640' height='360' \
src='<%= host_url %>/embed/<%= video.id %>?<%= host_params %>' frameborder='0'></iframe>" src='<%= host_url %>/embed/<%= video.id %>?<%= host_params %>' frameborder='0'></iframe>"
}; };
var player = videojs("player", options, function() { var player = videojs("player", options, function() {
this.hotkeys({ this.hotkeys({
volumeStep: 0.1, volumeStep: 0.1,
seekStep: 5, seekStep: 5,
enableModifiersForNumbers: false, enableModifiersForNumbers: false,
enableHoverScroll: true, enableHoverScroll: true,
customKeys: { customKeys: {
// Toggle play with K Key // Toggle play with K Key
play: { play: {
key: function(e) { key: function(e) {
return e.which === 75; return e.which === 75;
}, },
handler: function(player, options, e) { handler: function(player, options, e) {
if (player.paused()) { if (player.paused()) {
player.play(); player.play();
} else { } else {
player.pause(); player.pause();
} }
}
},
// Go backward 10 seconds
backward: {
key: function(e) {
return e.which === 74;
},
handler: function(player, options, e) {
player.currentTime(player.currentTime() - 10);
}
},
// Go forward 10 seconds
forward: {
key: function(e) {
return e.which === 76;
},
handler: function(player, options, e) {
player.currentTime(player.currentTime() + 10);
}
},
// Increase speed
increase_speed: {
key: function(e) {
return (e.which === 190 && e.shiftKey);
},
handler: function(player, _, e) {
size = options.playbackRates.length;
index = options.playbackRates.indexOf(player.playbackRate());
player.playbackRate(options.playbackRates[(index + 1) % size]);
}
},
// Decrease speed
decrease_speed: {
key: function(e) {
return (e.which === 188 && e.shiftKey);
},
handler: function(player, _, e) {
size = options.playbackRates.length;
index = options.playbackRates.indexOf(player.playbackRate());
player.playbackRate(options.playbackRates[(size + index - 1) % size]);
}
}
} }
}, });
// Go backward 5 seconds
backward: {
key: function(e) {
return e.which === 74;
},
handler: function(player, options, e) {
player.currentTime(player.currentTime() - 10);
}
},
// Go forward 5 seconds
forward: {
key: function(e) {
return e.which === 76;
},
handler: function(player, options, e) {
player.currentTime(player.currentTime() + 10);
}
},
// Increase speed
increase_speed: {
key: function(e) {
return (e.which === 190 && e.shiftKey);
},
handler: function(player, _, e) {
size = options.playbackRates.length;
index = options.playbackRates.indexOf(player.playbackRate());
player.playbackRate(options.playbackRates[(index + 1) % size]);
}
},
// Decrease speed
decrease_speed: {
key: function(e) {
return (e.which === 188 && e.shiftKey);
},
handler: function(player, _, e) {
size = options.playbackRates.length;
index = options.playbackRates.indexOf(player.playbackRate());
player.playbackRate(options.playbackRates[(size + index - 1) % size]);
}
}
}
});
}); });
player.on('error', function(event) { player.on('error', function(event) {
if (player.error().code === 2 || player.error().code === 4) { if (player.error().code === 2 || player.error().code === 4) {
setInterval(setTimeout(function (event) { setInterval(setTimeout(function (event) {
console.log("An error occured in the player, reloading..."); console.log('An error occured in the player, reloading...');
var currentTime = player.currentTime(); var currentTime = player.currentTime();
var playbackRate = player.playbackRate(); var playbackRate = player.playbackRate();
var paused = player.paused(); var paused = player.paused();
player.load(); player.load();
if (currentTime > 0.5) {
currentTime -= 0.5;
}
player.currentTime(currentTime);
player.playbackRate(playbackRate);
if (!paused) { if (currentTime > 0.5) {
player.play(); currentTime -= 0.5;
} }
}, 5000), 5000);
} player.currentTime(currentTime);
player.playbackRate(playbackRate);
if (!paused) {
player.play();
}
}, 5000), 5000);
}
}); });
<% if params.video_start > 0 || params.video_end > 0 %> <% if params.video_start > 0 || params.video_end > 0 %>
player.markers({ player.markers({
onMarkerReached: function(marker) { onMarkerReached: function(marker) {
if (marker.text === "End") { if (marker.text === 'End') {
if (player.loop()) { if (player.loop()) {
player.markers.prev("Start"); player.markers.prev('Start');
} else { } else {
player.pause(); player.pause();
} }
} }
}, },
markers: [ markers: [
{ time: <%= params.video_start %>, text: "Start" }, { time: <%= params.video_start %>, text: 'Start' },
<% if params.video_end < 0 %> <% if params.video_end < 0 %>
{ time: <%= video.info["length_seconds"].to_f - 0.5 %>, text: "End" } { time: <%= video.info["length_seconds"].to_f - 0.5 %>, text: 'End' }
<% else %> <% else %>
{ time: <%= params.video_end %>, text: "End" } { time: <%= params.video_end %>, text: 'End' }
<% end %> <% end %>
] ]
}); });
player.currentTime(<%= params.video_start %>); player.currentTime(<%= params.video_start %>);
@ -192,20 +194,20 @@ player.playbackRate(<%= params.speed %>);
var bpb = player.getChild('bigPlayButton'); var bpb = player.getChild('bigPlayButton');
if (bpb) { if (bpb) {
bpb.hide(); bpb.hide();
player.ready(function() { player.ready(function() {
new Promise(function(resolve, reject) { new Promise(function(resolve, reject) {
setTimeout(() => resolve(1), 1); setTimeout(() => resolve(1), 1);
}).then(function(result) { }).then(function(result) {
var promise = player.play(); var promise = player.play();
if (promise !== undefined) { if (promise !== undefined) {
promise.then(_ => { promise.then(_ => {
}).catch(error => { }).catch(error => {
bpb.show(); bpb.show();
}); });
} }
}); });
}); });
} }
@ -216,44 +218,42 @@ player.httpSourceSelector();
<% end %> <% end %>
<% if !params.listen && params.annotations %> <% if !params.listen && params.annotations %>
var video_container = document.getElementById("player"); var video_container = document.getElementById('player');
let xhr = new XMLHttpRequest(); let xhr = new XMLHttpRequest();
xhr.responseType = "text"; xhr.responseType = 'text';
xhr.timeout = 60000; xhr.timeout = 60000;
xhr.open("GET", "/api/v1/annotations/<%= video.id %>", true); xhr.open('GET', '/api/v1/annotations/<%= video.id %>', true);
xhr.send(); xhr.send();
xhr.onreadystatechange = function () { xhr.onreadystatechange = function () {
if (xhr.readyState === 4) { if (xhr.readyState === 4) {
if (xhr.status === 200) { if (xhr.status === 200) {
videojs.registerPlugin("youtubeAnnotationsPlugin", youtubeAnnotationsPlugin); videojs.registerPlugin('youtubeAnnotationsPlugin', youtubeAnnotationsPlugin);
if (!player.paused()) { if (!player.paused()) {
player.youtubeAnnotationsPlugin({annotationXml: xhr.response, videoContainer: video_container}); player.youtubeAnnotationsPlugin({annotationXml: xhr.response, videoContainer: video_container});
} else { } else {
player.one('play', function(event) { player.one('play', function(event) {
player.youtubeAnnotationsPlugin({annotationXml: xhr.response, videoContainer: video_container}); player.youtubeAnnotationsPlugin({annotationXml: xhr.response, videoContainer: video_container});
}); });
} }
}
} }
}
}; };
window.addEventListener("__ar_annotation_click", e => { window.addEventListener('__ar_annotation_click', e => {
const { url, target, seconds } = e.detail; const { url, target, seconds } = e.detail;
var path = new URL(url); var path = new URL(url);
if (path.href.startsWith("https://www.youtube.com/watch?") && seconds) { if (path.href.startsWith('https://www.youtube.com/watch?') && seconds) {
path.search += "&t=" + seconds; path.search += '&t=' + seconds;
} }
path = path.pathname + path.search; path = path.pathname + path.search;
if (target === "current") { if (target === 'current') {
window.location.href = path; window.location.href = path;
} } else if (target === 'new') {
else if (target === "new") { window.open(path, '_blank');
window.open(path, "_blank");
} }
}); });
<% end %> <% end %>

View File

@ -8,11 +8,13 @@
<script src="/js/videojs.hotkeys.min.js"></script> <script src="/js/videojs.hotkeys.min.js"></script>
<script src="/js/videojs-markers.min.js"></script> <script src="/js/videojs-markers.min.js"></script>
<script src="/js/videojs-share.min.js"></script> <script src="/js/videojs-share.min.js"></script>
<% if params.annotations %> <% if params.annotations %>
<link rel="stylesheet" href="/css/videojs-youtube-annotations.min.css"> <link rel="stylesheet" href="/css/videojs-youtube-annotations.min.css">
<script src="/js/videojs-youtube-annotations.min.js"></script> <script src="/js/videojs-youtube-annotations.min.js"></script>
<% end %> <% end %>
<% if params.listen || params.quality != "dash" %> <% if params.listen || params.quality != "dash" %>
<link rel="stylesheet" href="/css/quality-selector.css"> <link rel="stylesheet" href="/css/quality-selector.css">
<script src="/js/silvermine-videojs-quality-selector.min.js"></script> <script src="/js/silvermine-videojs-quality-selector.min.js"></script>
<% end %> <% end %>

View File

@ -1,22 +1,22 @@
<% if user %> <% if user %>
<% if subscriptions.includes? ucid %> <% if subscriptions.includes? ucid %>
<p> <p>
<form onsubmit="return false;" action="/subscription_ajax?action_remove_subscriptions=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>" method="post"> <form onsubmit="return false" action="/subscription_ajax?action_remove_subscriptions=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>" method="post">
<input type="hidden" name="csrf_token" value="<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>"> <input type="hidden" name="csrf_token" value="<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>">
<a id="subscribe" onclick="unsubscribe()" class="pure-button pure-button-primary" href="#"> <a id="subscribe" onclick="unsubscribe()" class="pure-button pure-button-primary" href="#">
<b><input style="all:unset" type="submit" value="<%= translate(locale, "Unsubscribe") %> | <%= sub_count_text %>"></b> <b><input style="all:unset" type="submit" value="<%= translate(locale, "Unsubscribe") %> | <%= sub_count_text %>"></b>
</a> </a>
</form> </form>
</p> </p>
<% else %> <% else %>
<p> <p>
<form onsubmit="return false;" action="/subscription_ajax?action_create_subscription_to_channel=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>" method="post"> <form onsubmit="return false" action="/subscription_ajax?action_create_subscription_to_channel=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>" method="post">
<input type="hidden" name="csrf_token" value="<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>"> <input type="hidden" name="csrf_token" value="<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>">
<a id="subscribe" onclick="subscribe()" class="pure-button pure-button-primary" href="#"> <a id="subscribe" onclick="subscribe()" class="pure-button pure-button-primary" href="#">
<b><input style="all:unset" type="submit" value="<%= translate(locale, "Subscribe") %> | <%= sub_count_text %>"></b> <b><input style="all:unset" type="submit" value="<%= translate(locale, "Subscribe") %> | <%= sub_count_text %>"></b>
</a> </a>
</form> </form>
</p> </p>
<% end %> <% end %>
<% else %> <% else %>
<p> <p>

View File

@ -1,27 +1,29 @@
subscribe_button = document.getElementById("subscribe"); subscribe_button = document.getElementById('subscribe');
if (subscribe_button.getAttribute('onclick')) { if (subscribe_button.getAttribute('onclick')) {
subscribe_button["href"] = "javascript:void(0)"; subscribe_button['href'] = 'javascript:void(0)';
} }
function subscribe(timeouts = 0) { function subscribe(timeouts = 0) {
subscribe_button = document.getElementById("subscribe"); subscribe_button = document.getElementById('subscribe');
if (timeouts > 10) { if (timeouts > 10) {
console.log("Failed to subscribe."); console.log('Failed to subscribe.');
return; return;
} }
var url = "/subscription_ajax?action_create_subscription_to_channel=1&redirect=false&c=<%= ucid %>&referer=<%= env.get("current_page") %>"; var url = '/subscription_ajax?action_create_subscription_to_channel=1&redirect=false' +
'&c=<%= ucid %>&referer=<%= env.get("current_page") %>';
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.responseType = "json"; xhr.responseType = 'json';
xhr.timeout = 20000; xhr.timeout = 20000;
xhr.open("POST", url, true); xhr.open('POST', url, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send("csrf_token=<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>"); xhr.send('csrf_token=<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>');
var fallback = subscribe_button.innerHTML; var fallback = subscribe_button.innerHTML;
subscribe_button.onclick = unsubscribe; subscribe_button.onclick = unsubscribe;
subscribe_button.innerHTML = '<b><%= translate(locale, "Unsubscribe").gsub("'", "\\'") %> | <%= sub_count_text %></b>' subscribe_button.innerHTML = '<b><%= translate(locale, "Unsubscribe").gsub("'", "\\'") %> | <%= sub_count_text %></b>';
xhr.onreadystatechange = function() { xhr.onreadystatechange = function() {
if (xhr.readyState == 4) { if (xhr.readyState == 4) {
@ -33,31 +35,31 @@ function subscribe(timeouts = 0) {
} }
xhr.ontimeout = function() { xhr.ontimeout = function() {
console.log("Subscribing timed out."); console.log('Subscribing timed out.');
subscribe(timeouts + 1); subscribe(timeouts + 1);
}; };
} }
function unsubscribe(timeouts = 0) { function unsubscribe(timeouts = 0) {
subscribe_button = document.getElementById("subscribe"); subscribe_button = document.getElementById('subscribe');
if (timeouts > 10) { if (timeouts > 10) {
console.log("Failed to subscribe"); console.log('Failed to subscribe');
return; return;
} }
var url = "/subscription_ajax?action_remove_subscriptions=1&redirect=false&c=<%= ucid %>&referer=<%= env.get("current_page") %>"; var url = '/subscription_ajax?action_remove_subscriptions=1&redirect=false' +
'&c=<%= ucid %>&referer=<%= env.get("current_page") %>';
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.responseType = "json"; xhr.responseType = 'json';
xhr.timeout = 20000; xhr.timeout = 20000;
xhr.open("POST", url, true); xhr.open('POST', url, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send("csrf_token=<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>"); xhr.send('csrf_token=<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>');
var fallback = subscribe_button.innerHTML; var fallback = subscribe_button.innerHTML;
subscribe_button.onclick = subscribe; subscribe_button.onclick = subscribe;
subscribe_button.innerHTML = '<b><%= translate(locale, "Subscribe").gsub("'", "\\'") %> | <%= sub_count_text %></b>' subscribe_button.innerHTML = '<b><%= translate(locale, "Subscribe").gsub("'", "\\'") %> | <%= sub_count_text %></b>';
xhr.onreadystatechange = function() { xhr.onreadystatechange = function() {
if (xhr.readyState == 4) { if (xhr.readyState == 4) {
@ -69,8 +71,7 @@ function unsubscribe(timeouts = 0) {
} }
xhr.ontimeout = function() { xhr.ontimeout = function() {
console.log("Unsubscribing timed out."); console.log('Unsubscribing timed out.');
unsubscribe(timeouts + 1); unsubscribe(timeouts + 1);
}; };
} }

View File

@ -27,24 +27,26 @@
<script> <script>
<% if plid %> <% if plid %>
function get_playlist(timeouts = 0) { function get_playlist(plid, timeouts = 0) {
if (timeouts > 10) { if (timeouts > 10) {
console.log("Failed to pull playlist"); console.log('Failed to pull playlist');
return; return;
} }
var plid = "<%= plid %>" if (plid.startsWith('RD')) {
var plid_url = '/api/v1/mixes/' + plid +
if (plid.startsWith("RD")) { '?continuation=<%= video.id %>' +
var plid_url = "/api/v1/mixes/<%= plid %>?continuation=<%= video.id %>&format=html&hl=<%= env.get("preferences").as(Preferences).locale %>"; '&format=html&hl=<%= env.get("preferences").as(Preferences).locale %>';
} else { } else {
var plid_url = "/api/v1/playlists/<%= plid %>?continuation=<%= video.id %>&format=html&hl=<%= env.get("preferences").as(Preferences).locale %>"; var plid_url = '/api/v1/playlists/' + plid +
'?continuation=<%= video.id %>' +
'&format=html&hl=<%= env.get("preferences").as(Preferences).locale %>';
} }
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.responseType = "json"; xhr.responseType = 'json';
xhr.timeout = 20000; xhr.timeout = 20000;
xhr.open("GET", plid_url, true); xhr.open('GET', plid_url, true);
xhr.send(); xhr.send();
xhr.onreadystatechange = function() { xhr.onreadystatechange = function() {
@ -52,18 +54,18 @@ function get_playlist(timeouts = 0) {
if (xhr.status == 200) { if (xhr.status == 200) {
if (xhr.response.nextVideo) { if (xhr.response.nextVideo) {
player.on('ended', function() { player.on('ended', function() {
location.assign("/embed/" location.assign('/watch?v=' + xhr.response.nextVideo +
+ xhr.response.nextVideo '&list=' + plid +
+ "?list=<%= plid %>"
<% if params.listen != preferences.listen %> <% if params.listen != preferences.listen %>
+ "&listen=<%= params.listen %>" '&listen=<%= params.listen %>' +
<% end %> <% end %>
<% if params.autoplay || params.continue_autoplay %> <% if params.autoplay || params.continue_autoplay %>
+ "&autoplay=1" '&autoplay=1' +
<% end %> <% end %>
<% if params.speed != preferences.speed %> <% if params.speed != preferences.speed %>
+ "&speed=<%= params.speed %>" '&speed=<%= params.speed %>' +
<% end %> <% end %>
''
); );
}); });
} }
@ -72,29 +74,29 @@ function get_playlist(timeouts = 0) {
}; };
xhr.ontimeout = function() { xhr.ontimeout = function() {
console.log("Pulling playlist timed out."); console.log('Pulling playlist timed out.');
get_playlist(timeouts + 1); get_playlist(plid, timeouts + 1);
}; };
} }
get_playlist(); get_playlist('<%= plid %>');
<% elsif video_series %> <% elsif video_series %>
player.on('ended', function() { player.on('ended', function() {
location.assign("/embed/" location.assign('/embed/<%= video_series.shift %>' +
+ "<%= video_series.shift %>" <% if !video_series.empty? %>
<% if !video_series.empty? %> '?playlist=<%= video_series.join(",") %>' +
+ "?playlist=<%= video_series.join(",") %>" <% end %>
<% end %> <% if params.listen != preferences.listen %>
<% if params.listen != preferences.listen %> '&listen=<%= params.listen %>' +
+ "&listen=<%= params.listen %>" <% end %>
<% end %> <% if params.autoplay || params.continue_autoplay %>
<% if params.autoplay || params.continue_autoplay %> '&autoplay=1' +
+ "&autoplay=1" <% end %>
<% end %> <% if params.speed != preferences.speed %>
<% if params.speed != preferences.speed %> '&speed=<%= params.speed %>' +
+ "&speed=<%= params.speed %>" <% end %>
<% end %> ''
); );
}); });
<% end %> <% end %>
</script> </script>

View File

@ -3,5 +3,5 @@
<% end %> <% end %>
<div class="h-box"> <div class="h-box">
<%= error_message %> <%= error_message %>
</div> </div>

View File

@ -6,12 +6,12 @@
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<h3><%= translate(locale, "`x` videos", %(<span id="count">#{user.watched.size}</span>)) %></h3> <h3><%= translate(locale, "`x` videos", %(<span id="count">#{user.watched.size}</span>)) %></h3>
</div> </div>
<div class="pure-u-1-3" style="text-align:center;"> <div class="pure-u-1-3" style="text-align:center">
<h3> <h3>
<a href="/feed/subscriptions"><%= translate(locale, "`x` subscriptions", %(<span id="count">#{user.subscriptions.size}</span>)) %></a> <a href="/feed/subscriptions"><%= translate(locale, "`x` subscriptions", %(<span id="count">#{user.subscriptions.size}</span>)) %></a>
</h3> </h3>
</div> </div>
<div class="pure-u-1-3" style="text-align:right;"> <div class="pure-u-1-3" style="text-align:right">
<h3> <h3>
<a href="/clear_watch_history"><%= translate(locale, "Clear watch history") %></a> <a href="/clear_watch_history"><%= translate(locale, "Clear watch history") %></a>
</h3> </h3>
@ -21,29 +21,28 @@
<div class="pure-g"> <div class="pure-g">
<% watched.each_slice(4) do |slice| %> <% watched.each_slice(4) do |slice| %>
<% slice.each do |item| %> <% slice.each do |item| %>
<div class="pure-u-1 pure-u-md-1-4"> <div class="pure-u-1 pure-u-md-1-4">
<div class="h-box"> <div class="h-box">
<a style="width:100%;" href="/watch?v=<%= item %>"> <a style="width:100%" href="/watch?v=<%= item %>">
<% if env.get("preferences").as(Preferences).thin_mode %> <% if !env.get("preferences").as(Preferences).thin_mode %>
<% else %> <div class="thumbnail">
<div class="thumbnail"> <img class="thumbnail" src="/vi/<%= item %>/mqdefault.jpg"/>
<img class="thumbnail" src="/vi/<%= item %>/mqdefault.jpg"/> <form onsubmit="return false;" action="/watch_ajax?action_mark_unwatched=1&id=<%= item %>&referer=<%= env.get("current_page") %>" method="post">
<form onsubmit="return false;" action="/watch_ajax?action_mark_unwatched=1&id=<%= item %>&referer=<%= env.get("current_page") %>" method="post"> <input type="hidden" name="csrf_token" value="<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>">
<input type="hidden" name="csrf_token" value="<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>"> <p class="watched">
<p class="watched"> <a onclick="mark_unwatched(this)" data-id="<%= item %>" href="#">
<a onclick="mark_unwatched(this)" data-id="<%= item %>" href="#"> <button type="submit" style="all:unset">
<button type="submit" style="all:unset"> <i class="icon ion-md-trash"></i>
<i class="icon ion-md-trash"></i> </button>
</button> </a>
</a> </p>
</p> </form>
</form> </div>
</div> <p></p>
<p></p> <% end %>
<% end %> </a>
</a> </div>
</div> </div>
</div>
<% end %> <% end %>
<% end %> <% end %>
</div> </div>
@ -52,22 +51,23 @@
function mark_unwatched(target) { function mark_unwatched(target) {
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode; var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
tile.style.display = "none"; tile.style.display = "none";
var count = document.getElementById("count") var count = document.getElementById('count')
count.innerText = count.innerText - 1; count.innerText = count.innerText - 1;
var url = "/watch_ajax?action_mark_unwatched=1&redirect=false&id=" + target.getAttribute("data-id"); var url = '/watch_ajax?action_mark_unwatched=1&redirect=false' +
'&id=' + target.getAttribute('data-id');
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.responseType = "json"; xhr.responseType = 'json';
xhr.timeout = 20000; xhr.timeout = 20000;
xhr.open("POST", url, true); xhr.open('POST', url, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send("csrf_token=<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>"); xhr.send('csrf_token=<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>');
xhr.onreadystatechange = function() { xhr.onreadystatechange = function() {
if (xhr.readyState == 4) { if (xhr.readyState == 4) {
if (xhr.status != 200) { if (xhr.status != 200) {
count.innerText = count.innerText - 1 + 2; count.innerText = count.innerText - 1 + 2;
tile.style.display = ""; tile.style.display = '';
} }
} }
} }
@ -76,18 +76,18 @@ function mark_unwatched(target) {
<div class="pure-g h-box"> <div class="pure-g h-box">
<div class="pure-u-1 pure-u-lg-1-5"> <div class="pure-u-1 pure-u-lg-1-5">
<% if page >= 2 %> <% if page >= 2 %>
<a href="/feed/history?page=<%= page - 1 %>"> <a href="/feed/history?page=<%= page - 1 %>">
<%= translate(locale, "Previous page") %> <%= translate(locale, "Previous page") %>
</a> </a>
<% end %> <% end %>
</div> </div>
<div class="pure-u-1 pure-u-lg-3-5"></div> <div class="pure-u-1 pure-u-lg-3-5"></div>
<div style="text-align:right;" class="pure-u-1 pure-u-lg-1-5"> <div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
<% if watched.size >= limit %> <% if watched.size >= limit %>
<a href="/feed/history?page=<%= page + 1 %>"> <a href="/feed/history?page=<%= page + 1 %>">
<%= translate(locale, "Next page") %> <%= translate(locale, "Next page") %>
</a> </a>
<% end %> <% end %>
</div> </div>
</div> </div>

View File

@ -2,8 +2,8 @@
<html lang="en-US"> <html lang="en-US">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
</head> </head>
<body> <body>
@ -59,7 +59,7 @@
<td> <td>
<a href="http://www.jclark.com/xml/copying.txt">Expat</a> <a href="http://www.jclark.com/xml/copying.txt">Expat</a>
</td> </td>
<td> <td>
<a href="https://github.com/jfujita/videojs-http-source-selector"><%= translate(locale, "source") %></a> <a href="https://github.com/jfujita/videojs-http-source-selector"><%= translate(locale, "source") %></a>
</td> </td>
@ -136,4 +136,4 @@
</tr> </tr>
</table> </table>
</body> </body>
</html> </html>

View File

@ -18,95 +18,97 @@
</a> </a>
</div> </div>
</div> </div>
<hr> <hr>
<% if account_type == "invidious" %> <% if account_type == "invidious" %>
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.escape(referer) %>&type=invidious" method="post"> <form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.escape(referer) %>&type=invidious" method="post">
<fieldset> <fieldset>
<% if email %> <% if email %>
<input name="email" type="hidden" value="<%= email %>"> <input name="email" type="hidden" value="<%= email %>">
<% else %> <% else %>
<label for="email"><%= translate(locale, "User ID") %> :</label> <label for="email"><%= translate(locale, "User ID") %> :</label>
<input required class="pure-input-1" name="email" type="text" placeholder="<%= translate(locale, "User ID") %>"> <input required class="pure-input-1" name="email" type="text" placeholder="<%= translate(locale, "User ID") %>">
<% end %>
<% if password %>
<input name="password" type="hidden" value="<%= password %>">
<% else %>
<label for="password"><%= translate(locale, "Password") %> :</label>
<input required class="pure-input-1" name="password" type="password" placeholder="<%= translate(locale, "Password") %>">
<% end %>
<% if captcha %>
<% case captcha_type when %>
<% when "image" %>
<% captcha = captcha.not_nil! %>
<img style="width:100%" src='<%= captcha[:question] %>'/>
<% captcha[:tokens].each_with_index do |token, i| %>
<input type="hidden" name="token[<%= i %>]" value="<%= URI.escape(token) %>">
<% end %> <% end %>
<input type="hidden" name="captcha_type" value="image">
<label for="answer"><%= translate(locale, "Time (h:mm:ss):") %></label> <% if password %>
<input type="text" name="answer" type="text" placeholder="h:mm:ss"> <input name="password" type="hidden" value="<%= password %>">
<% when "text" %> <% else %>
<% captcha = captcha.not_nil! %> <label for="password"><%= translate(locale, "Password") %> :</label>
<% captcha[:tokens].each_with_index do |token, i| %> <input required class="pure-input-1" name="password" type="password" placeholder="<%= translate(locale, "Password") %>">
<input type="hidden" name="token[<%= i %>]" value="<%= URI.escape(token) %>">
<% end %> <% end %>
<input type="hidden" name="captcha_type" value="text">
<label for="answer"><%= captcha[:question] %></label>
<input type="text" name="answer" type="text" placeholder="<%= translate(locale, "Answer") %>">
<% end %>
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary"> <% if captcha %>
<%= translate(locale, "Register") %> <% case captcha_type when %>
</button> <% when "image" %>
<% captcha = captcha.not_nil! %>
<img style="width:100%" src='<%= captcha[:question] %>'/>
<% captcha[:tokens].each_with_index do |token, i| %>
<input type="hidden" name="token[<%= i %>]" value="<%= URI.escape(token) %>">
<% end %>
<input type="hidden" name="captcha_type" value="image">
<label for="answer"><%= translate(locale, "Time (h:mm:ss):") %></label>
<input type="text" name="answer" type="text" placeholder="h:mm:ss">
<% when "text" %>
<% captcha = captcha.not_nil! %>
<% captcha[:tokens].each_with_index do |token, i| %>
<input type="hidden" name="token[<%= i %>]" value="<%= URI.escape(token) %>">
<% end %>
<input type="hidden" name="captcha_type" value="text">
<label for="answer"><%= captcha[:question] %></label>
<input type="text" name="answer" type="text" placeholder="<%= translate(locale, "Answer") %>">
<% end %>
<% case captcha_type when %> <button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
<% when "image" %> <%= translate(locale, "Register") %>
<label>
<button type="submit" name="change_type" class="pure-button pure-button-primary" value="text">
<%= translate(locale, "Text CAPTCHA") %>
</button> </button>
</label>
<% when "text" %> <% case captcha_type when %>
<label> <% when "image" %>
<button type="submit" name="change_type" class="pure-button pure-button-primary" value="image"> <label>
<%= translate(locale, "Image CAPTCHA") %> <button type="submit" name="change_type" class="pure-button pure-button-primary" value="text">
<%= translate(locale, "Text CAPTCHA") %>
</button>
</label>
<% when "text" %>
<label>
<button type="submit" name="change_type" class="pure-button pure-button-primary" value="image">
<%= translate(locale, "Image CAPTCHA") %>
</button>
</label>
<% end %>
<% else %>
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
<%= translate(locale, "Sign In") %>/<%= translate(locale, "Register") %>
</button> </button>
</label> <% end %>
<% end %> </fieldset>
<% else %> </form>
<button type="submit" name="action" value="signin" class="pure-button pure-button-primary">
<%= translate(locale, "Sign In") %>/<%= translate(locale, "Register") %>
</button>
<% end %>
</fieldset>
</form>
<% elsif account_type == "google" %> <% elsif account_type == "google" %>
<form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.escape(referer) %>&type=google" method="post"> <form class="pure-form pure-form-stacked" action="/login?referer=<%= URI.escape(referer) %>&type=google" method="post">
<fieldset> <fieldset>
<% if email %> <% if email %>
<input name="email" type="hidden" value="<%= email %>"> <input name="email" type="hidden" value="<%= email %>">
<% else %> <% else %>
<label for="email"><%= translate(locale, "E-mail") %> :</label> <label for="email"><%= translate(locale, "E-mail") %> :</label>
<input required class="pure-input-1" name="email" type="email" placeholder="<%= translate(locale, "E-mail") %>"> <input required class="pure-input-1" name="email" type="email" placeholder="<%= translate(locale, "E-mail") %>">
<% end %> <% end %>
<% if password %> <% if password %>
<input name="password" type="hidden" value="<%= password %>"> <input name="password" type="hidden" value="<%= password %>">
<% else %> <% else %>
<label for="password"><%= translate(locale, "Password") %> :</label> <label for="password"><%= translate(locale, "Password") %> :</label>
<input required class="pure-input-1" name="password" type="password" placeholder="<%= translate(locale, "Password") %>"> <input required class="pure-input-1" name="password" type="password" placeholder="<%= translate(locale, "Password") %>">
<% end %> <% end %>
<% if tfa %> <% if tfa %>
<label for="tfa"><%= translate(locale, "Google verification code") %> :</label> <label for="tfa"><%= translate(locale, "Google verification code") %> :</label>
<input required class="pure-input-1" name="tfa" type="text" placeholder="<%= translate(locale, "Google verification code") %>"> <input required class="pure-input-1" name="tfa" type="text" placeholder="<%= translate(locale, "Google verification code") %>">
<% end %> <% end %>
<button type="submit" class="pure-button pure-button-primary"><%= translate(locale, "Sign In") %></button> <button type="submit" class="pure-button pure-button-primary"><%= translate(locale, "Sign In") %></button>
</fieldset> </fieldset>
</form> </form>
<% end %> <% end %>
</div> </div>
</div> </div>

View File

@ -6,7 +6,7 @@
<div class="pure-u-2-3"> <div class="pure-u-2-3">
<h3><%= mix.title %></h3> <h3><%= mix.title %></h3>
</div> </div>
<div class="pure-u-1-3" style="text-align:right;"> <div class="pure-u-1-3" style="text-align:right">
<h3> <h3>
<a href="/feed/playlist/<%= mix.id %>"><i class="icon ion-logo-rss"></i></a> <a href="/feed/playlist/<%= mix.id %>"><i class="icon ion-logo-rss"></i></a>
</h3> </h3>
@ -14,9 +14,9 @@
</div> </div>
<div class="pure-g"> <div class="pure-g">
<% mix.videos.each_slice(4) do |slice| %> <% mix.videos.each_slice(4) do |slice| %>
<% slice.each do |item| %> <% slice.each do |item| %>
<%= rendered "components/item" %> <%= rendered "components/item" %>
<% end %>
<% end %> <% end %>
<% end %>
</div> </div>

View File

@ -7,12 +7,13 @@
<div class="pure-u-2-3"> <div class="pure-u-2-3">
<h3><%= playlist.title %></h3> <h3><%= playlist.title %></h3>
</div> </div>
<div class="pure-u-1-3" style="text-align:right;"> <div class="pure-u-1-3" style="text-align:right">
<h3> <h3>
<a href="/feed/playlist/<%= plid %>"><i class="icon ion-logo-rss"></i></a> <a href="/feed/playlist/<%= plid %>"><i class="icon ion-logo-rss"></i></a>
</h3> </h3>
</div> </div>
</div> </div>
<div class="pure-g h-box"> <div class="pure-g h-box">
<div class="pure-u-1 pure-u-md-1-4"> <div class="pure-u-1 pure-u-md-1-4">
<a href="/channel/<%= playlist.ucid %>"> <a href="/channel/<%= playlist.ucid %>">
@ -26,27 +27,27 @@
</div> </div>
<div class="pure-g"> <div class="pure-g">
<% videos.each_slice(4) do |slice| %> <% videos.each_slice(4) do |slice| %>
<% slice.each do |item| %> <% slice.each do |item| %>
<%= rendered "components/item" %> <%= rendered "components/item" %>
<% end %>
<% end %> <% end %>
<% end %>
</div> </div>
<div class="pure-g h-box"> <div class="pure-g h-box">
<div class="pure-u-1 pure-u-lg-1-5"> <div class="pure-u-1 pure-u-lg-1-5">
<% if page >= 2 %> <% if page >= 2 %>
<a href="/playlist?list=<%= playlist.id %>&page=<%= page - 1 %>"> <a href="/playlist?list=<%= playlist.id %>&page=<%= page - 1 %>">
<%= translate(locale, "Previous page") %> <%= translate(locale, "Previous page") %>
</a> </a>
<% end %> <% end %>
</div> </div>
<div class="pure-u-1 pure-u-lg-3-5"></div> <div class="pure-u-1 pure-u-lg-3-5"></div>
<div style="text-align:right;" class="pure-u-1 pure-u-lg-1-5"> <div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
<% if videos.size == 100 %> <% if videos.size == 100 %>
<a href="/playlist?list=<%= playlist.id %>&page=<%= page + 1 %>"> <a href="/playlist?list=<%= playlist.id %>&page=<%= page + 1 %>">
<%= translate(locale, "Next page") %> <%= translate(locale, "Next page") %>
</a> </a>
<% end %> <% end %>
</div> </div>
</div> </div>

View File

@ -6,7 +6,7 @@
<div class="pure-u-2-3"> <div class="pure-u-2-3">
<h3><%= author %></h3> <h3><%= author %></h3>
</div> </div>
<div class="pure-u-1-3" style="text-align:right;"> <div class="pure-u-1-3" style="text-align:right">
<h3> <h3>
<a href="/feed/channel/<%= ucid %>"><i class="icon ion-logo-rss"></i></a> <a href="/feed/channel/<%= ucid %>"><i class="icon ion-logo-rss"></i></a>
</h3> </h3>
@ -14,8 +14,8 @@
</div> </div>
<div class="h-box"> <div class="h-box">
<% sub_count_text = number_to_short_text(sub_count) %> <% sub_count_text = number_to_short_text(sub_count) %>
<%= rendered "components/subscribe_widget" %> <%= rendered "components/subscribe_widget" %>
</div> </div>
<div class="pure-g h-box"> <div class="pure-g h-box">
@ -28,25 +28,24 @@
</div> </div>
<div class="pure-u-1 pure-md-1-3"> <div class="pure-u-1 pure-md-1-3">
<% if !auto_generated %> <% if !auto_generated %>
<b><%= translate(locale, "Playlists") %></b> <b><%= translate(locale, "Playlists") %></b>
<% end %> <% end %>
</div> </div>
</div> </div>
<div class="pure-u-1-3"></div>
<div class="pure-u-1-3"> <div class="pure-u-1-3">
</div> <div class="pure-g" style="text-align:right">
<div class="pure-u-1-3"> <% {"last", "oldest", "newest"}.each do |sort| %>
<div class="pure-g" style="text-align:right;"> <div class="pure-u-1 pure-md-1-3">
<% {"last", "oldest", "newest"}.each do |sort| %> <% if sort_by == sort %>
<div class="pure-u-1 pure-md-1-3"> <b><%= translate(locale, sort) %></b>
<% if sort_by == sort %> <% else %>
<b><%= translate(locale, sort) %></b> <a href="/channel/<%= ucid %>/playlists?sort_by=<%= sort %>">
<% else %> <%= translate(locale, sort) %>
<a href="/channel/<%= ucid %>/playlists?sort_by=<%= sort %>"> </a>
<%= translate(locale, sort) %> <% end %>
</a> </div>
<% end %> <% end %>
</div>
<% end %>
</div> </div>
</div> </div>
</div> </div>
@ -56,21 +55,21 @@
</div> </div>
<div class="pure-g"> <div class="pure-g">
<% items.each_slice(4) do |slice| %> <% items.each_slice(4) do |slice| %>
<% slice.each do |item| %> <% slice.each do |item| %>
<%= rendered "components/item" %> <%= rendered "components/item" %>
<% end %>
<% end %> <% end %>
<% end %>
</div> </div>
<div class="pure-g h-box"> <div class="pure-g h-box">
<div class="pure-u-1 pure-u-md-4-5"></div> <div class="pure-u-1 pure-u-md-4-5"></div>
<div style="text-align:right;" class="pure-u-1 pure-u-lg-1-5"> <div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
<% if items.size >= 28 %> <% if items.size >= 28 %>
<a href="/channel/<%= ucid %>/playlists?continuation=<%= continuation %><% if sort_by != "last" %>&sort_by=<%= sort_by %><% end %>"> <a href="/channel/<%= ucid %>/playlists?continuation=<%= continuation %><% if sort_by != "last" %>&sort_by=<%= sort_by %><% end %>">
<%= translate(locale, "Next page") %> <%= translate(locale, "Next page") %>
</a> </a>
<% end %> <% end %>
</div> </div>
</div> </div>

View File

@ -1,14 +1,20 @@
<% content_for "header" do %> <% content_for "header" do %>
<meta name="description" content="<%= translate(locale, "An alternative front-end to YouTube") %>"> <meta name="description" content="<%= translate(locale, "An alternative front-end to YouTube") %>">
<title><% if config.default_home != "Popular" %><%= translate(locale, "Popular") %> - <% end %>Invidious</title> <title>
<% if config.default_home != "Popular" %>
<%= translate(locale, "Popular") %> - Invidious
<% else %>
Invidious
<% end %>
</title>
<% end %> <% end %>
<%= rendered "components/feed_menu" %> <%= rendered "components/feed_menu" %>
<div class="pure-g"> <div class="pure-g">
<% popular_videos.each_slice(4) do |slice| %> <% popular_videos.each_slice(4) do |slice| %>
<% slice.each do |item| %> <% slice.each do |item| %>
<%= rendered "components/item" %> <%= rendered "components/item" %>
<% end %> <% end %>
<% end %> <% end %>
</div> </div>

View File

@ -46,18 +46,18 @@ function update_value(element) {
<div class="pure-control-group"> <div class="pure-control-group">
<label for="speed"><%= translate(locale, "Default speed: ") %></label> <label for="speed"><%= translate(locale, "Default speed: ") %></label>
<select name="speed" id="speed"> <select name="speed" id="speed">
<% {2.0, 1.5, 1.25, 1.0, 0.75, 0.5, 0.25}.each do |option| %> <% {2.0, 1.5, 1.25, 1.0, 0.75, 0.5, 0.25}.each do |option| %>
<option <% if preferences.speed == option %> selected <% end %>><%= option %></option> <option <% if preferences.speed == option %> selected <% end %>><%= option %></option>
<% end %> <% end %>
</select> </select>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="quality"><%= translate(locale, "Preferred video quality: ") %></label> <label for="quality"><%= translate(locale, "Preferred video quality: ") %></label>
<select name="quality" id="quality"> <select name="quality" id="quality">
<% {"dash", "hd720", "medium", "small"}.each do |option| %> <% {"dash", "hd720", "medium", "small"}.each do |option| %>
<option value="<%= option %>" <% if preferences.quality == option %> selected <% end %>><%= translate(locale, option) %></option> <option value="<%= option %>" <% if preferences.quality == option %> selected <% end %>><%= translate(locale, option) %></option>
<% end %> <% end %>
</select> </select>
</div> </div>
@ -70,22 +70,22 @@ function update_value(element) {
<div class="pure-control-group"> <div class="pure-control-group">
<label for="comments[0]"><%= translate(locale, "Default comments: ") %></label> <label for="comments[0]"><%= translate(locale, "Default comments: ") %></label>
<% preferences.comments.each_with_index do |comments, index| %> <% preferences.comments.each_with_index do |comments, index| %>
<select name="comments[<%= index %>]" id="comments[<%= index %>]"> <select name="comments[<%= index %>]" id="comments[<%= index %>]">
<% {"", "youtube", "reddit"}.each do |option| %> <% {"", "youtube", "reddit"}.each do |option| %>
<option value="<%= option %>" <% if preferences.comments[index] == option %> selected <% end %>><%= translate(locale, option) %></option> <option value="<%= option %>" <% if preferences.comments[index] == option %> selected <% end %>><%= translate(locale, option) %></option>
<% end %> <% end %>
</select> </select>
<% end %> <% end %>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="captions[0]"><%= translate(locale, "Default captions: ") %></label> <label for="captions[0]"><%= translate(locale, "Default captions: ") %></label>
<% preferences.captions.each_with_index do |caption, index| %> <% preferences.captions.each_with_index do |caption, index| %>
<select class="pure-u-1-6" name="captions[<%= index %>]" id="captions[<%= index %>]"> <select class="pure-u-1-6" name="captions[<%= index %>]" id="captions[<%= index %>]">
<% CAPTION_LANGUAGES.each do |option| %> <% CAPTION_LANGUAGES.each do |option| %>
<option value="<%= option %>" <% if preferences.captions[index] == option %> selected <% end %>><%= translate(locale, option) %></option> <option value="<%= option %>" <% if preferences.captions[index] == option %> selected <% end %>><%= translate(locale, option) %></option>
<% end %> <% end %>
</select> </select>
<% end %> <% end %>
</div> </div>
@ -104,9 +104,9 @@ function update_value(element) {
<div class="pure-control-group"> <div class="pure-control-group">
<label for="locale"><%= translate(locale, "Language: ") %></label> <label for="locale"><%= translate(locale, "Language: ") %></label>
<select name="locale" id="locale"> <select name="locale" id="locale">
<% LOCALES.each_key do |option| %> <% LOCALES.each_key do |option| %>
<option value="<%= option %>" <% if preferences.locale == option %> selected <% end %>><%= option %></option> <option value="<%= option %>" <% if preferences.locale == option %> selected <% end %>><%= option %></option>
<% end %> <% end %>
</select> </select>
</div> </div>
@ -121,131 +121,131 @@ function update_value(element) {
</div> </div>
<% if env.get? "user" %> <% if env.get? "user" %>
<legend><%= translate(locale, "Subscription preferences") %></legend> <legend><%= translate(locale, "Subscription preferences") %></legend>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="annotations_subscribed"><%= translate(locale, "Show annotations by default for subscribed channels? ") %></label> <label for="annotations_subscribed"><%= translate(locale, "Show annotations by default for subscribed channels? ") %></label>
<input name="annotations_subscribed" id="annotations_subscribed" type="checkbox" <% if preferences.annotations_subscribed %>checked<% end %>> <input name="annotations_subscribed" id="annotations_subscribed" type="checkbox" <% if preferences.annotations_subscribed %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="redirect_feed"><%= translate(locale, "Redirect homepage to feed: ") %></label> <label for="redirect_feed"><%= translate(locale, "Redirect homepage to feed: ") %></label>
<input name="redirect_feed" id="redirect_feed" type="checkbox" <% if preferences.redirect_feed %>checked<% end %>> <input name="redirect_feed" id="redirect_feed" type="checkbox" <% if preferences.redirect_feed %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="max_results"><%= translate(locale, "Number of videos shown in feed: ") %></label> <label for="max_results"><%= translate(locale, "Number of videos shown in feed: ") %></label>
<input name="max_results" id="max_results" type="number" value="<%= preferences.max_results %>"> <input name="max_results" id="max_results" type="number" value="<%= preferences.max_results %>">
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="sort"><%= translate(locale, "Sort videos by: ") %></label> <label for="sort"><%= translate(locale, "Sort videos by: ") %></label>
<select name="sort" id="sort"> <select name="sort" id="sort">
<% {"published", "published - reverse", "alphabetically", "alphabetically - reverse", "channel name", "channel name - reverse"}.each do |option| %> <% {"published", "published - reverse", "alphabetically", "alphabetically - reverse", "channel name", "channel name - reverse"}.each do |option| %>
<option value="<%= option %>" <% if preferences.sort == option %> selected <% end %>><%= translate(locale, option) %></option> <option value="<%= option %>" <% if preferences.sort == option %> selected <% end %>><%= translate(locale, option) %></option>
<% end %> <% end %>
</select> </select>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<% if preferences.unseen_only %> <% if preferences.unseen_only %>
<label for="latest_only"><%= translate(locale, "Only show latest unwatched video from channel: ") %></label> <label for="latest_only"><%= translate(locale, "Only show latest unwatched video from channel: ") %></label>
<% else %> <% else %>
<label for="latest_only"><%= translate(locale, "Only show latest video from channel: ") %></label> <label for="latest_only"><%= translate(locale, "Only show latest video from channel: ") %></label>
<% end %> <% end %>
<input name="latest_only" id="latest_only" type="checkbox" <% if preferences.latest_only %>checked<% end %>> <input name="latest_only" id="latest_only" type="checkbox" <% if preferences.latest_only %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="unseen_only"><%= translate(locale, "Only show unwatched: ") %></label> <label for="unseen_only"><%= translate(locale, "Only show unwatched: ") %></label>
<input name="unseen_only" id="unseen_only" type="checkbox" <% if preferences.unseen_only %>checked<% end %>> <input name="unseen_only" id="unseen_only" type="checkbox" <% if preferences.unseen_only %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="notifications_only"><%= translate(locale, "Only show notifications (if there are any): ") %></label> <label for="notifications_only"><%= translate(locale, "Only show notifications (if there are any): ") %></label>
<input name="notifications_only" id="notifications_only" type="checkbox" <% if preferences.notifications_only %>checked<% end %>> <input name="notifications_only" id="notifications_only" type="checkbox" <% if preferences.notifications_only %>checked<% end %>>
</div> </div>
<% end %> <% end %>
<% if env.get?("user") && config.admins.includes? env.get?("user").as(User).email %> <% if env.get?("user") && config.admins.includes? env.get?("user").as(User).email %>
<legend><%= translate(locale, "Administrator preferences") %></legend> <legend><%= translate(locale, "Administrator preferences") %></legend>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="default_home"><%= translate(locale, "Default homepage: ") %></label> <label for="default_home"><%= translate(locale, "Default homepage: ") %></label>
<select name="default_home" id="default_home"> <select name="default_home" id="default_home">
<% {"Popular", "Top", "Trending", "Subscriptions"}.each do |option| %> <% {"Popular", "Top", "Trending", "Subscriptions"}.each do |option| %>
<option value="<%= option %>" <% if config.default_home == option %> selected <% end %>><%= translate(locale, option) %></option> <option value="<%= option %>" <% if config.default_home == option %> selected <% end %>><%= translate(locale, option) %></option>
<% end %> <% end %>
</select> </select>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="feed_menu"><%= translate(locale, "Feed menu: ") %></label> <label for="feed_menu"><%= translate(locale, "Feed menu: ") %></label>
<% 4.times do |index| %> <% 4.times do |index| %>
<select name="feed_menu[<%= index %>]" id="feed_menu[<%= index %>]"> <select name="feed_menu[<%= index %>]" id="feed_menu[<%= index %>]">
<% {"", "Popular", "Top", "Trending", "Subscriptions"}.each do |option| %> <% {"", "Popular", "Top", "Trending", "Subscriptions"}.each do |option| %>
<option value="<%= option %>" <% if config.feed_menu[index]? == option %> selected <% end %>><%= translate(locale, option) %></option> <option value="<%= option %>" <% if config.feed_menu[index]? == option %> selected <% end %>><%= translate(locale, option) %></option>
<% end %> <% end %>
</select> </select>
<% end %> <% end %>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="top_enabled"><%= translate(locale, "Top enabled? ") %></label> <label for="top_enabled"><%= translate(locale, "Top enabled? ") %></label>
<input name="top_enabled" id="top_enabled" type="checkbox" <% if config.top_enabled %>checked<% end %>> <input name="top_enabled" id="top_enabled" type="checkbox" <% if config.top_enabled %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="captcha_enabled"><%= translate(locale, "CAPTCHA enabled? ") %></label> <label for="captcha_enabled"><%= translate(locale, "CAPTCHA enabled? ") %></label>
<input name="captcha_enabled" id="captcha_enabled" type="checkbox" <% if config.captcha_enabled %>checked<% end %>> <input name="captcha_enabled" id="captcha_enabled" type="checkbox" <% if config.captcha_enabled %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="login_enabled"><%= translate(locale, "Login enabled? ") %></label> <label for="login_enabled"><%= translate(locale, "Login enabled? ") %></label>
<input name="login_enabled" id="login_enabled" type="checkbox" <% if config.login_enabled %>checked<% end %>> <input name="login_enabled" id="login_enabled" type="checkbox" <% if config.login_enabled %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="registration_enabled"><%= translate(locale, "Registration enabled? ") %></label> <label for="registration_enabled"><%= translate(locale, "Registration enabled? ") %></label>
<input name="registration_enabled" id="registration_enabled" type="checkbox" <% if config.registration_enabled %>checked<% end %>> <input name="registration_enabled" id="registration_enabled" type="checkbox" <% if config.registration_enabled %>checked<% end %>>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<label for="statistics_enabled"><%= translate(locale, "Report statistics? ") %></label> <label for="statistics_enabled"><%= translate(locale, "Report statistics? ") %></label>
<input name="statistics_enabled" id="statistics_enabled" type="checkbox" <% if config.statistics_enabled %>checked<% end %>> <input name="statistics_enabled" id="statistics_enabled" type="checkbox" <% if config.statistics_enabled %>checked<% end %>>
</div> </div>
<% end %> <% end %>
<% if env.get? "user" %> <% if env.get? "user" %>
<legend><%= translate(locale, "Data preferences") %></legend> <legend><%= translate(locale, "Data preferences") %></legend>
<div class="pure-control-group"> <div class="pure-control-group">
<a href="/clear_watch_history?referer=<%= URI.escape(referer) %>"><%= translate(locale, "Clear watch history") %></a> <a href="/clear_watch_history?referer=<%= URI.escape(referer) %>"><%= translate(locale, "Clear watch history") %></a>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<a href="/change_password?referer=<%= URI.escape(referer) %>"><%= translate(locale, "Change password") %></a> <a href="/change_password?referer=<%= URI.escape(referer) %>"><%= translate(locale, "Change password") %></a>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<a href="/data_control?referer=<%= URI.escape(referer) %>"><%= translate(locale, "Import/export data") %></a> <a href="/data_control?referer=<%= URI.escape(referer) %>"><%= translate(locale, "Import/export data") %></a>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<a href="/subscription_manager"><%= translate(locale, "Manage subscriptions") %></a> <a href="/subscription_manager"><%= translate(locale, "Manage subscriptions") %></a>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<a href="/token_manager"><%= translate(locale, "Manage tokens") %></a> <a href="/token_manager"><%= translate(locale, "Manage tokens") %></a>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<a href="/feed/history"><%= translate(locale, "Watch history") %></a> <a href="/feed/history"><%= translate(locale, "Watch history") %></a>
</div> </div>
<div class="pure-control-group"> <div class="pure-control-group">
<a href="/delete_account?referer=<%= URI.escape(referer) %>"><%= translate(locale, "Delete account") %></a> <a href="/delete_account?referer=<%= URI.escape(referer) %>"><%= translate(locale, "Delete account") %></a>
</div> </div>
<% end %> <% end %>
<div class="pure-controls"> <div class="pure-controls">

View File

@ -3,73 +3,73 @@
<% end %> <% end %>
<div class="h-box"> <div class="h-box">
<%= Markdown.to_html(<<-END_PRIVACY_POLICY <%= Markdown.to_html(<<-END_PRIVACY_POLICY
## Privacy ## Privacy
This document concerns what data you provide to this website, the purpose of the data, how the data is stored, and how the data can be removed. This document concerns what data you provide to this website, the purpose of the data, how the data is stored, and how the data can be removed.
### Data you directly provide ### Data you directly provide
Data that you provide to the website for the purpose of the site's operation (for example: an account name, account password, or channel subscription) will be stored in the website's database until the user decides to remove it. This data will not be intentionally shared with anyone or anything. Data that you provide to the website for the purpose of the site's operation (for example: an account name, account password, or channel subscription) will be stored in the website's database until the user decides to remove it. This data will not be intentionally shared with anyone or anything.
Information stored about a registered user is limited to: Information stored about a registered user is limited to:
- a list of session tokens for remaining logged in across devices - a list of session tokens for remaining logged in across devices
- the last time an account was updated (to provide accurate notifications) - the last time an account was updated (to provide accurate notifications)
- a list of video IDs identifying notifications from a user's subscriptions - a list of video IDs identifying notifications from a user's subscriptions
- a list of channel UCIDs the user is subscribed to - a list of channel UCIDs the user is subscribed to
- a user ID (for persistent storage of subscriptions and preferences) - a user ID (for persistent storage of subscriptions and preferences)
- a json object containing user preferences - a json object containing user preferences
- a hashed password if applicable (not present on google accounts) - a hashed password if applicable (not present on google accounts)
- a randomly generated token for providing an RSS feed of a user's subscriptions - a randomly generated token for providing an RSS feed of a user's subscriptions
- a list of video IDs identifying watched videos - a list of video IDs identifying watched videos
The above list reflects [this code](https://github.com/omarroth/invidious/blob/master/src/invidious/users.cr#L14-L51). The above list reflects [this code](https://github.com/omarroth/invidious/blob/master/src/invidious/users.cr#L14-L51).
Users can clear their watch history using the [clear watch history](/clear_watch_history) page. Users can clear their watch history using the [clear watch history](/clear_watch_history) page.
If a user is logged in with a Google account, no password will ever be stored. This website uses the session token provided by Google to identify a user, but does not store the information required to make requests on a user's behalf without their knowledge or consent. If a user is logged in with a Google account, no password will ever be stored. This website uses the session token provided by Google to identify a user, but does not store the information required to make requests on a user's behalf without their knowledge or consent.
### Data you passively provide ### Data you passively provide
When you request any resource from this website (for example: a page, a font, an image, or an API endpoint) information about the request may be logged. When you request any resource from this website (for example: a page, a font, an image, or an API endpoint) information about the request may be logged.
Information about a request is limited to: Information about a request is limited to:
- the time the request was made - the time the request was made
- the status code of the response - the status code of the response
- the method of the request - the method of the request
- the requested URL - the requested URL
- how long it took to complete the request. - how long it took to complete the request.
No identifying information is logged, such as the visitor's cookie, user-agent, or IP address. Here are a couple lines to serve as an example: No identifying information is logged, such as the visitor's cookie, user-agent, or IP address. Here are a couple lines to serve as an example:
``` ```
2019-01-19 16:37:47 +00:00 200 GET /api/v1/comments/xrlETJYzH-c?format=html&hl=en-US 1345.88ms 2019-01-19 16:37:47 +00:00 200 GET /api/v1/comments/xrlETJYzH-c?format=html&hl=en-US 1345.88ms
2019-01-19 16:37:53 +00:00 200 GET /vi/r5P-f5arPXE/maxres.jpg 1085.41ms 2019-01-19 16:37:53 +00:00 200 GET /vi/r5P-f5arPXE/maxres.jpg 1085.41ms
2019-01-19 16:37:54 +00:00 200 GET /watch 7.04ms 2019-01-19 16:37:54 +00:00 200 GET /watch 7.04ms
``` ```
This website does not store the visitor's user-agent or IP address and does not use fingerprinting, advertisements, or tracking of any form. This website does not store the visitor's user-agent or IP address and does not use fingerprinting, advertisements, or tracking of any form.
This website provides links to googlevideo.com to provide audio and video playback. googlevideo.com is owned by Google and is subject to their [privacy policy](https://policies.google.com/privacy). This website provides links to googlevideo.com to provide audio and video playback. googlevideo.com is owned by Google and is subject to their [privacy policy](https://policies.google.com/privacy).
### Data stored in your browser ### Data stored in your browser
This website uses browser cookies to authenticate registered users. This data consists of: This website uses browser cookies to authenticate registered users. This data consists of:
- An account token to keep you logged into the website between visits, which is sent when any page is loaded while you are logged in - An account token to keep you logged into the website between visits, which is sent when any page is loaded while you are logged in
This website also provides an option to store site preferences, such as the theme or locale, without an account. Using this feature will store a cookie in the visitor's browser containing their preferences. This cookie is sent on every request and does not contain any identifying information. This website also provides an option to store site preferences, such as the theme or locale, without an account. Using this feature will store a cookie in the visitor's browser containing their preferences. This cookie is sent on every request and does not contain any identifying information.
You can remove this data from your browser by logging out of this website, or by using your browser's cookie-related controls to delete the data. You can remove this data from your browser by logging out of this website, or by using your browser's cookie-related controls to delete the data.
### Removal of data ### Removal of data
To remove data stored in your browser, you can log out of the website, or you can use your browser's cookie-related controls to delete the data. To remove data stored in your browser, you can log out of the website, or you can use your browser's cookie-related controls to delete the data.
To remove data that has been stored in the website's database, you can use the [delete my account](/delete_account) page. To remove data that has been stored in the website's database, you can use the [delete my account](/delete_account) page.
END_PRIVACY_POLICY END_PRIVACY_POLICY
) )
%> %>
</div> </div>

View File

@ -3,27 +3,27 @@
<% end %> <% end %>
<div class="pure-g"> <div class="pure-g">
<% videos.each_slice(4) do |slice| %> <% videos.each_slice(4) do |slice| %>
<% slice.each do |item| %> <% slice.each do |item| %>
<%= rendered "components/item" %> <%= rendered "components/item" %>
<% end %>
<% end %> <% end %>
<% end %>
</div> </div>
<div class="pure-g h-box"> <div class="pure-g h-box">
<div class="pure-u-1 pure-u-lg-1-5"> <div class="pure-u-1 pure-u-lg-1-5">
<% if page >= 2 %> <% if page >= 2 %>
<a href="/search?q=<%= HTML.escape(query.not_nil!) %>&page=<%= page - 1 %>"> <a href="/search?q=<%= HTML.escape(query.not_nil!) %>&page=<%= page - 1 %>">
<%= translate(locale, "Previous page") %> <%= translate(locale, "Previous page") %>
</a> </a>
<% end %> <% end %>
</div> </div>
<div class="pure-u-1 pure-u-lg-3-5"></div> <div class="pure-u-1 pure-u-lg-3-5"></div>
<div style="text-align:right;" class="pure-u-1 pure-u-lg-1-5"> <div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
<% if count >= 20 %> <% if count >= 20 %>
<a href="/search?q=<%= HTML.escape(query.not_nil!) %>&page=<%= page + 1 %>"> <a href="/search?q=<%= HTML.escape(query.not_nil!) %>&page=<%= page + 1 %>">
<%= translate(locale, "Next page") %> <%= translate(locale, "Next page") %>
</a> </a>
<% end %> <% end %>
</div> </div>
</div> </div>

View File

@ -5,70 +5,78 @@
<div class="pure-g h-box"> <div class="pure-g h-box">
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<h3> <h3>
<a href="/feed/subscriptions"><%= translate(locale, "`x` subscriptions", %(<span id="count">#{subscriptions.size}</span>)) %></a> <a href="/feed/subscriptions">
<%= translate(locale, "`x` subscriptions", %(<span id="count">#{subscriptions.size}</span>)) %>
</a>
</h3> </h3>
</div> </div>
<div class="pure-u-1-3" style="text-align:center"> <div class="pure-u-1-3" style="text-align:center">
<h3> <h3>
<a href="/feed/history"><%= translate(locale, "Watch history") %></a> <a href="/feed/history">
<%= translate(locale, "Watch history") %>
</a>
</h3> </h3>
</div> </div>
<div class="pure-u-1-3" style="text-align:right"> <div class="pure-u-1-3" style="text-align:right">
<h3> <h3>
<a href="/data_control?referer=<%= referer %>"><%= translate(locale, "Import/export") %></a> <a href="/data_control?referer=<%= referer %>">
<%= translate(locale, "Import/export") %>
</a>
</h3> </h3>
</div> </div>
</div> </div>
<% subscriptions.each do |channel| %> <% subscriptions.each do |channel| %>
<div class="h-box"> <div class="h-box">
<div class="pure-g<% if channel.deleted %> deleted <% end %>"> <div class="pure-g<% if channel.deleted %> deleted <% end %>">
<div class="pure-u-2-5"> <div class="pure-u-2-5">
<h3 style="padding-left:0.5em"> <h3 style="padding-left:0.5em">
<a href="/channel/<%= channel.id %>"><%= channel.author %></a> <a href="/channel/<%= channel.id %>"><%= channel.author %></a>
</h3> </h3>
</div>
<div class="pure-u-2-5"></div>
<div class="pure-u-1-5" style="text-align:right">
<h3 style="padding-right:0.5em">
<form onsubmit="return false" action="/subscription_ajax?action_remove_subscriptions=1&c=<%= channel.id %>&referer=<%= env.get("current_page") %>" method="post">
<input type="hidden" name="csrf_token" value="<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>">
<a onclick="remove_subscription(this)" data-ucid="<%= channel.id %>" href="#">
<input style="all:unset" type="submit" value="<%= translate(locale, "unsubscribe") %>">
</a>
</form>
</h3>
</div>
</div> </div>
<div class="pure-u-2-5"></div>
<div class="pure-u-1-5" style="text-align:right">
<h3 style="padding-right:0.5em">
<form onsubmit="return false" action="/subscription_ajax?action_remove_subscriptions=1&c=<%= channel.id %>&referer=<%= env.get("current_page") %>" method="post">
<input type="hidden" name="csrf_token" value="<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>">
<a onclick="remove_subscription(this)" data-ucid="<%= channel.id %>" href="#">
<input style="all:unset" type="submit" value="<%= translate(locale, "unsubscribe") %>">
</a>
</form>
</h3>
</div>
</div>
<% if subscriptions[-1].author != channel.author %> <% if subscriptions[-1].author != channel.author %>
<hr> <hr>
<% end %> <% end %>
</div> </div>
<% end %> <% end %>
<script> <script>
function remove_subscription(target) { function remove_subscription(target) {
var row = target.parentNode.parentNode.parentNode.parentNode.parentNode; var row = target.parentNode.parentNode.parentNode.parentNode.parentNode;
row.style.display = "none"; row.style.display = 'none';
var count = document.getElementById("count") var count = document.getElementById('count');
count.innerText = count.innerText - 1; count.innerText = count.innerText - 1;
var url = "/subscription_ajax?action_remove_subscriptions=1&redirect=false&referer=<%= env.get("current_page") %>&c=" + target.getAttribute("data-ucid"); var url = '/subscription_ajax?action_remove_subscriptions=1&redirect=false' +
'&referer=<%= env.get("current_page") %>' +
'&c=' + target.getAttribute('data-ucid');
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.responseType = "json"; xhr.responseType = 'json';
xhr.timeout = 20000; xhr.timeout = 20000;
xhr.open("POST", url, true); xhr.open('POST', url, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send("csrf_token=<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>"); xhr.send('csrf_token=<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>');
xhr.onreadystatechange = function() { xhr.onreadystatechange = function() {
if (xhr.readyState == 4) { if (xhr.readyState == 4) {
if (xhr.status != 200) { if (xhr.status != 200) {
count.innerText = count.innerText - 1 + 2; count.innerText = parseInt(count.innerText) + 1;
row.style.display = ""; row.style.display = '';
} }
} }
} }
} }
</script> </script>

View File

@ -11,32 +11,34 @@
<a href="/subscription_manager"><%= translate(locale, "Manage subscriptions") %></a> <a href="/subscription_manager"><%= translate(locale, "Manage subscriptions") %></a>
</h3> </h3>
</div> </div>
<div class="pure-u-1-3" style="text-align:center;"> <div class="pure-u-1-3" style="text-align:center">
<h3> <h3>
<a href="/feed/history"><%= translate(locale, "Watch history") %></a> <a href="/feed/history"><%= translate(locale, "Watch history") %></a>
</h3> </h3>
</div> </div>
<div class="pure-u-1-3" style="text-align:right;"> <div class="pure-u-1-3" style="text-align:right">
<h3> <h3>
<a href="/feed/private?token=<%= token %>"><i class="icon ion-logo-rss"></i></a> <a href="/feed/private?token=<%= token %>"><i class="icon ion-logo-rss"></i></a>
</h3> </h3>
</div> </div>
</div> </div>
<center><%= translate(locale, "`x` unseen notifications", "#{notifications.size}") %></center> <center>
<%= translate(locale, "`x` unseen notifications", "#{notifications.size}") %>
</center>
<% if !notifications.empty? %> <% if !notifications.empty? %>
<div class="h-box"> <div class="h-box">
<hr> <hr>
</div> </div>
<% end %> <% end %>
<div class="pure-g"> <div class="pure-g">
<% notifications.each_slice(4) do |slice| %> <% notifications.each_slice(4) do |slice| %>
<% slice.each do |item| %> <% slice.each do |item| %>
<%= rendered "components/item" %> <%= rendered "components/item" %>
<% end %> <% end %>
<% end %> <% end %>
</div> </div>
<div class="h-box"> <div class="h-box">
@ -44,30 +46,31 @@
</div> </div>
<div class="pure-g"> <div class="pure-g">
<% videos.each_slice(4) do |slice| %> <% videos.each_slice(4) do |slice| %>
<% slice.each do |item| %> <% slice.each do |item| %>
<%= rendered "components/item" %> <%= rendered "components/item" %>
<% end %> <% end %>
<% end %> <% end %>
</div> </div>
<script> <script>
function mark_watched(target) { function mark_watched(target) {
var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode; var tile = target.parentNode.parentNode.parentNode.parentNode.parentNode;
tile.style.display = "none"; tile.style.display = 'none';
var url = "/watch_ajax?action_mark_watched=1&redirect=false&id=" + target.getAttribute("data-id"); var url = '/watch_ajax?action_mark_watched=1&redirect=false' +
'&id=' + target.getAttribute('data-id');
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.responseType = "json"; xhr.responseType = 'json';
xhr.timeout = 20000; xhr.timeout = 20000;
xhr.open("POST", url, true); xhr.open('POST', url, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send("csrf_token=<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>"); xhr.send('csrf_token=<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>');
xhr.onreadystatechange = function() { xhr.onreadystatechange = function() {
if (xhr.readyState == 4) { if (xhr.readyState == 4) {
if (xhr.status != 200) { if (xhr.status != 200) {
tile.style.display = ""; tile.style.display = '';
} }
} }
} }
@ -76,14 +79,14 @@ function mark_watched(target) {
<div class="pure-g h-box"> <div class="pure-g h-box">
<div class="pure-u-1 pure-u-lg-1-5"> <div class="pure-u-1 pure-u-lg-1-5">
<% if page >= 2 %> <% if page >= 2 %>
<a href="/feed/subscriptions?max_results=<%= max_results %>&page=<%= page - 1 %>"> <a href="/feed/subscriptions?max_results=<%= max_results %>&page=<%= page - 1 %>">
<%= translate(locale, "Previous page") %> <%= translate(locale, "Previous page") %>
</a> </a>
<% end %> <% end %>
</div> </div>
<div class="pure-u-1 pure-u-lg-3-5"></div> <div class="pure-u-1 pure-u-lg-3-5"></div>
<div style="text-align:right;" class="pure-u-1 pure-u-lg-1-5"> <div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
<% if (videos.size + notifications.size) == max_results %> <% if (videos.size + notifications.size) == max_results %>
<a href="/feed/subscriptions?max_results=<%= max_results %>&page=<%= page + 1 %>"> <a href="/feed/subscriptions?max_results=<%= max_results %>&page=<%= page + 1 %>">
<%= translate(locale, "Next page") %> <%= translate(locale, "Next page") %>

View File

@ -38,75 +38,78 @@
<div class="pure-u-1 pure-u-md-12-24 searchbar"> <div class="pure-u-1 pure-u-md-12-24 searchbar">
<form class="pure-form" action="/search" method="get"> <form class="pure-form" action="/search" method="get">
<fieldset> <fieldset>
<input type="search" style="width:100%;" name="q" placeholder="<%= translate(locale, "search") %>" value="<%= env.get?("search").try {|x| HTML.escape(x.as(String)) } || env.params.query["q"]?.try {|x| HTML.escape(x)} %>"> <input type="search" style="width:100%" name="q" placeholder="<%= translate(locale, "search") %>" value="<%= env.get?("search").try {|x| HTML.escape(x.as(String)) } || env.params.query["q"]?.try {|x| HTML.escape(x)} %>">
</fieldset> </fieldset>
</form> </form>
</div> </div>
<div class="pure-u-1 pure-u-md-8-24 user-field"> <div class="pure-u-1 pure-u-md-8-24 user-field">
<% if env.get? "user" %> <% if env.get? "user" %>
<div class="pure-u-1-4"> <div class="pure-u-1-4">
<a href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading"> <a href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
<% if env.get("preferences").as(Preferences).dark_mode %> <% if env.get("preferences").as(Preferences).dark_mode %>
<i class="icon ion-ios-sunny"></i> <i class="icon ion-ios-sunny"></i>
<% else %> <% else %>
<i class="icon ion-ios-moon"></i> <i class="icon ion-ios-moon"></i>
<% end %>
</a>
</div>
<div class="pure-u-1-4">
<a title="<%= translate(locale, "Subscriptions") %>" href="/feed/subscriptions" class="pure-menu-heading">
<% notification_count = env.get("user").as(User).notifications.size %>
<% if notification_count > 0 %>
<%= notification_count %> <i class="icon ion-ios-notifications"></i>
<% else %>
<i class="icon ion-ios-notifications-outline"></i>
<% end %>
</a>
</div>
<div class="pure-u-1-4">
<a title="<%= translate(locale, "Preferences") %>" href="/preferences?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
<i class="icon ion-ios-cog"></i>
</a>
</div>
<div class="pure-u-1-4">
<form action="/signout?referer=<%= env.get?("current_page") %>" method="post">
<input type="hidden" name="csrf_token" value="<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>">
<a class="pure-menu-heading" href="#">
<input style="all:unset" type="submit" value="<%= translate(locale, "Log out") %>">
</a>
</form>
</div>
<% else %>
<div class="pure-u-1-3">
<a href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
<% if env.get("preferences").as(Preferences).dark_mode %>
<i class="icon ion-ios-sunny"></i>
<% else %>
<i class="icon ion-ios-moon"></i>
<% end %>
</a>
</div>
<div class="pure-u-1-3">
<a title="<%= translate(locale, "Preferences") %>" href="/preferences?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
<i class="icon ion-ios-cog"></i>
</a>
</div>
<% if config.login_enabled %>
<div class="pure-u-1-3">
<a href="/login?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
<%= translate(locale, "Log in") %>
</a>
</div>
<% end %> <% end %>
</a> <% end %>
</div>
<div class="pure-u-1-4">
<a title="<%= translate(locale, "Subscriptions") %>" href="/feed/subscriptions" class="pure-menu-heading">
<% notification_count = env.get("user").as(User).notifications.size %>
<% if notification_count > 0 %>
<%= notification_count %> <i class="icon ion-ios-notifications"></i>
<% else %>
<i class="icon ion-ios-notifications-outline"></i>
<% end %>
</a>
</div>
<div class="pure-u-1-4">
<a title="<%= translate(locale, "Preferences") %>" href="/preferences?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
<i class="icon ion-ios-cog"></i>
</a>
</div>
<div class="pure-u-1-4">
<form action="/signout?referer=<%= env.get?("current_page") %>" method="post">
<input type="hidden" name="csrf_token" value="<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>">
<a class="pure-menu-heading" href="#">
<input style="all:unset" type="submit" value="<%= translate(locale, "Log out") %>">
</a>
</form>
</div>
<% else %>
<div class="pure-u-1-3">
<a href="/toggle_theme?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
<% if env.get("preferences").as(Preferences).dark_mode %>
<i class="icon ion-ios-sunny"></i>
<% else %>
<i class="icon ion-ios-moon"></i>
<% end %>
</a>
</div>
<div class="pure-u-1-3">
<a title="<%= translate(locale, "Preferences") %>" href="/preferences?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
<i class="icon ion-ios-cog"></i>
</a>
</div>
<% if config.login_enabled %>
<div class="pure-u-1-3">
<a href="/login?referer=<%= env.get?("current_page") %>" class="pure-menu-heading">
<%= translate(locale, "Log in") %>
</a>
</div>
<% end %>
<% end %>
</div> </div>
</div> </div>
<% if CONFIG.banner %> <% if CONFIG.banner %>
<div class="h-box"> <div class="h-box">
<h3><%= CONFIG.banner %></h3> <h3><%= CONFIG.banner %></h3>
</div> </div>
<% end %> <% end %>
<%= content %> <%= content %>
<div class="footer"> <div class="footer">
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-md-1-3"> <div class="pure-u-1 pure-u-md-1-3">
@ -116,10 +119,12 @@
</div> </div>
<div class="pure-u-1 pure-u-md-1-3"> <div class="pure-u-1 pure-u-md-1-3">
<i class="icon ion-logo-bitcoin"></i> <i class="icon ion-logo-bitcoin"></i>
BTC: 356DpZyMXu6rYd55Yqzjs29n79kGKWcYrY</div> BTC: 356DpZyMXu6rYd55Yqzjs29n79kGKWcYrY
</div>
<div class="pure-u-1 pure-u-md-1-3"> <div class="pure-u-1 pure-u-md-1-3">
<i class="icon ion-logo-bitcoin"></i> <i class="icon ion-logo-bitcoin"></i>
BCH: qq4ptclkzej5eza6a50et5ggc58hxsq5aylqut2npk</div> BCH: qq4ptclkzej5eza6a50et5ggc58hxsq5aylqut2npk
</div>
<div class="pure-u-1 pure-u-md-1-3"> <div class="pure-u-1 pure-u-md-1-3">
<i class="icon ion-logo-usd"></i> <i class="icon ion-logo-usd"></i>
<a href="https://liberapay.com/omarroth">Liberapay</a> <a href="https://liberapay.com/omarroth">Liberapay</a>
@ -141,7 +146,8 @@
<i class="icon ion-logo-github"></i> <i class="icon ion-logo-github"></i>
<%= translate(locale, "Current version: ") %> <%= CURRENT_VERSION %>-<%= CURRENT_COMMIT %> <%= translate(locale, "Current version: ") %> <%= CURRENT_VERSION %>-<%= CURRENT_COMMIT %>
<i class="icon ion-logo-github"></i> <i class="icon ion-logo-github"></i>
<%= CURRENT_BRANCH %></div> <%= CURRENT_BRANCH %>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -40,7 +40,7 @@
</div> </div>
<% if tokens[-1].try &.[:session]? != token[:session] %> <% if tokens[-1].try &.[:session]? != token[:session] %>
<hr> <hr>
<% end %> <% end %>
</div> </div>
<% end %> <% end %>
@ -48,25 +48,27 @@
<script> <script>
function revoke_token(target) { function revoke_token(target) {
var row = target.parentNode.parentNode.parentNode.parentNode.parentNode; var row = target.parentNode.parentNode.parentNode.parentNode.parentNode;
row.style.display = "none"; row.style.display = 'none';
var count = document.getElementById("count") var count = document.getElementById('count');
count.innerText = count.innerText - 1; count.innerText = count.innerText - 1;
var url = "/token_ajax?action_revoke_token=1&redirect=false&referer=<%= env.get("current_page") %>&session=" + target.getAttribute("data-session"); var url = '/token_ajax?action_revoke_token=1&redirect=false' +
'&referer=<%= env.get("current_page") %>' +
'&session=' + target.getAttribute('data-session');
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.responseType = "json"; xhr.responseType = 'json';
xhr.timeout = 20000; xhr.timeout = 20000;
xhr.open("POST", url, true); xhr.open('POST', url, true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.send("csrf_token=<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>"); xhr.send('csrf_token=<%= URI.escape(env.get?("csrf_token").try &.as(String) || "") %>');
xhr.onreadystatechange = function() { xhr.onreadystatechange = function() {
if (xhr.readyState == 4) { if (xhr.readyState == 4) {
if (xhr.status != 200) { if (xhr.status != 200) {
count.innerText = count.innerText - 1 + 2; count.innerText = parseInt(count.innerText) + 1;
row.style.display = ""; row.style.display = '';
} }
} }
} }
} }
</script> </script>

View File

@ -1,14 +1,20 @@
<% content_for "header" do %> <% content_for "header" do %>
<meta name="description" content="<%= translate(locale, "An alternative front-end to YouTube") %>"> <meta name="description" content="<%= translate(locale, "An alternative front-end to YouTube") %>">
<title><% if config.default_home != "Top" %><%= translate(locale, "Top") %> - <% end %>Invidious</title> <title>
<% if config.default_home != "Top" %>
<%= translate(locale, "Top") %> - Invidious
<% else %>
Invidious
<% end %>
</title>
<% end %> <% end %>
<%= rendered "components/feed_menu" %> <%= rendered "components/feed_menu" %>
<div class="pure-g"> <div class="pure-g">
<% top_videos.each_slice(4) do |slice| %> <% top_videos.each_slice(4) do |slice| %>
<% slice.each do |item| %> <% slice.each do |item| %>
<%= rendered "components/item" %> <%= rendered "components/item" %>
<% end %> <% end %>
<% end %> <% end %>
</div> </div>

View File

@ -1,6 +1,12 @@
<% content_for "header" do %> <% content_for "header" do %>
<meta name="description" content="<%= translate(locale, "An alternative front-end to YouTube") %>"> <meta name="description" content="<%= translate(locale, "An alternative front-end to YouTube") %>">
<title><% if config.default_home != "Trending" %><%= translate(locale, "Trending") %> - <% end %>Invidious</title> <title>
<% if config.default_home != "Trending" %>
<%= translate(locale, "Trending") %> - Invidious
<% else %>
Invidious
<% end %>
</title>
<% end %> <% end %>
<%= rendered "components/feed_menu" %> <%= rendered "components/feed_menu" %>
@ -8,19 +14,21 @@
<div class="pure-g h-box"> <div class="pure-g h-box">
<div style="align-self:flex-end" class="pure-u-2-3"> <div style="align-self:flex-end" class="pure-u-2-3">
<% if plid %> <% if plid %>
<a href="/playlist?list=<%= plid %>"><%= translate(locale, "View as playlist") %></a> <a href="/playlist?list=<%= plid %>">
<%= translate(locale, "View as playlist") %>
</a>
<% end %> <% end %>
</div> </div>
<div class="pure-u-1-3"> <div class="pure-u-1-3">
<div class="pure-g" style="text-align:right;"> <div class="pure-g" style="text-align:right">
<% {"Default", "Music", "Gaming", "News", "Movies"}.each do |option| %> <% {"Default", "Music", "Gaming", "News", "Movies"}.each do |option| %>
<div class="pure-u-1 pure-md-1-3"> <div class="pure-u-1 pure-md-1-3">
<% if trending_type == option %> <% if trending_type == option %>
<b><%= translate(locale, option) %></b> <b><%= translate(locale, option) %></b>
<% else %> <% else %>
<a href="/feed/trending?type=<%= option %>&region=<%= region %>"> <a href="/feed/trending?type=<%= option %>&region=<%= region %>">
<%= translate(locale, option) %> <%= translate(locale, option) %>
</a> </a>
<% end %> <% end %>
</div> </div>
<% end %> <% end %>
@ -33,9 +41,9 @@
</div> </div>
<div class="pure-g"> <div class="pure-g">
<% trending.each_slice(4) do |slice| %> <% trending.each_slice(4) do |slice| %>
<% slice.each do |item| %> <% slice.each do |item| %>
<%= rendered "components/item" %> <%= rendered "components/item" %>
<% end %> <% end %>
<% end %> <% end %>
</div> </div>

View File

@ -28,106 +28,114 @@
<% end %> <% end %>
<div id="player-container" class="h-box"> <div id="player-container" class="h-box">
<%= rendered "components/player" %> <%= rendered "components/player" %>
</div> </div>
<div class="h-box"> <div class="h-box">
<h1> <h1>
<%= HTML.escape(video.title) %> <%= HTML.escape(video.title) %>
<% if params.listen %> <% if params.listen %>
<a title="<%=translate(locale, "Video mode")%>" href="/watch?<%= env.params.query %>&listen=0"> <a title="<%=translate(locale, "Video mode")%>" href="/watch?<%= env.params.query %>&listen=0">
<i class="icon ion-ios-videocam"></i> <i class="icon ion-ios-videocam"></i>
</a> </a>
<% else %> <% else %>
<a title="<%=translate(locale, "Audio mode")%>" href="/watch?<%= env.params.query %>&listen=1"> <a title="<%=translate(locale, "Audio mode")%>" href="/watch?<%= env.params.query %>&listen=1">
<i class="icon ion-md-headset"></i> <i class="icon ion-md-headset"></i>
</a> </a>
<% end %>
</h1>
<% if !video.is_listed %>
<h3>
<i class="icon ion-ios-lock"></i> <%= translate(locale, "Unlisted") %>
</h3>
<% end %>
<% if !reason.empty? %>
<h3>
<%= reason %>
</h3>
<% end %> <% end %>
</h1>
<% if !video.is_listed %>
<h3><i class="icon ion-ios-lock"></i> <%= translate(locale, "Unlisted") %></h3>
<% end %>
<% if !reason.empty? %>
<h3><%= reason %></h3>
<% end %>
</div> </div>
<div class="pure-g"> <div class="pure-g">
<div class="pure-u-1 pure-u-lg-1-5"> <div class="pure-u-1 pure-u-lg-1-5">
<div class="h-box"> <div class="h-box">
<p><a href="https://www.youtube.com/watch?v=<%= video.id %>"><%= translate(locale, "Watch on YouTube") %></a></p>
<p> <p>
<% if params.annotations %> <a href="https://www.youtube.com/watch?v=<%= video.id %>"><%= translate(locale, "Watch on YouTube") %></a>
<a href="/watch?<%= env.params.query %>&iv_load_policy=3"> </p>
<%= translate(locale, "Hide annotations") %> <p>
</a> <% if params.annotations %>
<% else %> <a href="/watch?<%= env.params.query %>&iv_load_policy=3">
<a href="/watch?<%= env.params.query %>&iv_load_policy=1"> <%= translate(locale, "Hide annotations") %>
<%=translate(locale, "Show annotations")%> </a>
</a> <% else %>
<% end %> <a href="/watch?<%= env.params.query %>&iv_load_policy=1">
<%=translate(locale, "Show annotations")%>
</a>
<% end %>
</p> </p>
<% if CONFIG.dmca_content.includes? video.id %> <% if CONFIG.dmca_content.includes? video.id %>
<p>Download is disabled.</p> <p><%= translate(locale, "Download is disabled.") %></p>
<% else %> <% else %>
<form class="pure-form pure-form-stacked" action="/latest_version" method="get" rel="noopener" target="_blank"> <form class="pure-form pure-form-stacked" action="/latest_version" method="get" rel="noopener" target="_blank">
<div class="pure-control-group"> <div class="pure-control-group">
<label for="download_widget"><%= translate(locale, "Download as: ") %></label> <label for="download_widget"><%= translate(locale, "Download as: ") %></label>
<select style="width:100%" name="download_widget" id="download_widget"> <select style="width:100%" name="download_widget" id="download_widget">
<% fmt_stream.each do |option| %> <% fmt_stream.each do |option| %>
<option value='{"id":"<%= video.id %>","itag":"<%= option["itag"] %>","title":"<%= URI.escape(video.title) %>-<%= video.id %>.<%= option["type"].split(";")[0].split("/")[1] %>"}'> <option value='{"id":"<%= video.id %>","itag":"<%= option["itag"] %>","title":"<%= URI.escape(video.title) %>-<%= video.id %>.<%= option["type"].split(";")[0].split("/")[1] %>"}'>
<%= itag_to_metadata?(option["itag"]).try &.["height"]? || "~240" %>p - <%= option["type"].split(";")[0] %> <%= itag_to_metadata?(option["itag"]).try &.["height"]? || "~240" %>p - <%= option["type"].split(";")[0] %>
</option> </option>
<% end %> <% end %>
<% video_streams.each do |option| %> <% video_streams.each do |option| %>
<option value='{"id":"<%= video.id %>","itag":"<%= option["itag"] %>","title":"<%= URI.escape(video.title) %>-<%= video.id %>.<%= option["type"].split(";")[0].split("/")[1] %>"}'> <option value='{"id":"<%= video.id %>","itag":"<%= option["itag"] %>","title":"<%= URI.escape(video.title) %>-<%= video.id %>.<%= option["type"].split(";")[0].split("/")[1] %>"}'>
<%= option["quality_label"] %> - <%= option["type"].split(";")[0] %> @ <%= option["fps"] %>fps - video only <%= option["quality_label"] %> - <%= option["type"].split(";")[0] %> @ <%= option["fps"] %>fps - video only
</option> </option>
<% end %> <% end %>
<% audio_streams.each do |option| %> <% audio_streams.each do |option| %>
<option value='{"id":"<%= video.id %>","itag":"<%= option["itag"] %>","title":"<%= URI.escape(video.title) %>-<%= video.id %>.<%= option["type"].split(";")[0].split("/")[1] %>"}'> <option value='{"id":"<%= video.id %>","itag":"<%= option["itag"] %>","title":"<%= URI.escape(video.title) %>-<%= video.id %>.<%= option["type"].split(";")[0].split("/")[1] %>"}'>
<%= option["type"].split(";")[0] %> @ <%= option["bitrate"] %>k - audio only <%= option["type"].split(";")[0] %> @ <%= option["bitrate"] %>k - audio only
</option> </option>
<% end %> <% end %>
<% captions.each do |caption| %> <% captions.each do |caption| %>
<option value='{"id":"<%= video.id %>","label":"<%= caption.name.simpleText %>","title":"<%= URI.escape(video.title) %>-<%= video.id %>.<%= caption.languageCode %>.vtt"}'> <option value='{"id":"<%= video.id %>","label":"<%= caption.name.simpleText %>","title":"<%= URI.escape(video.title) %>-<%= video.id %>.<%= caption.languageCode %>.vtt"}'>
<%= translate(locale, "Subtitles - `x` (.vtt)", caption.name.simpleText) %> <%= translate(locale, "Subtitles - `x` (.vtt)", caption.name.simpleText) %>
</option> </option>
<% end %> <% end %>
</select> </select>
</div> </div>
<button type="submit" class="pure-button pure-button-primary"> <button type="submit" class="pure-button pure-button-primary">
<b><%= translate(locale, "Download") %></b> <b><%= translate(locale, "Download") %></b>
</button> </button>
</form> </form>
<% end %> <% end %>
<p><i class="icon ion-ios-eye"></i> <%= number_with_separator(video.views) %></p> <p><i class="icon ion-ios-eye"></i> <%= number_with_separator(video.views) %></p>
<p><i class="icon ion-ios-thumbs-up"></i> <%= number_with_separator(video.likes) %></p> <p><i class="icon ion-ios-thumbs-up"></i> <%= number_with_separator(video.likes) %></p>
<p><i class="icon ion-ios-thumbs-down"></i> <%= number_with_separator(video.dislikes) %></p> <p><i class="icon ion-ios-thumbs-down"></i> <%= number_with_separator(video.dislikes) %></p>
<p id="Genre"><%= translate(locale, "Genre: ") %> <p id="genre"><%= translate(locale, "Genre: ") %>
<% if video.genre_url.empty? %> <% if video.genre_url.empty? %>
<%= video.genre %> <%= video.genre %>
<% else %> <% else %>
<a href="<%= video.genre_url %>"><%= video.genre %></a> <a href="<%= video.genre_url %>"><%= video.genre %></a>
<% end %> <% end %>
</p> </p>
<% if !video.license.empty? %> <% if !video.license.empty? %>
<p id="License"><%= translate(locale, "License: ") %><%= video.license %></p> <p id="license"><%= translate(locale, "License: ") %><%= video.license %></p>
<% end %> <% end %>
<p id="FamilyFriendly"><%= translate(locale, "Family friendly? ") %><%= translate_bool(locale, video.is_family_friendly) %></p> <p id="family_friendly"><%= translate(locale, "Family friendly? ") %><%= translate_bool(locale, video.is_family_friendly) %></p>
<p id="Wilson"><%= translate(locale, "Wilson score: ") %><%= video.wilson_score.round(4) %></p> <p id="wilson"><%= translate(locale, "Wilson score: ") %><%= video.wilson_score.round(4) %></p>
<p id="Rating"><%= translate(locale, "Rating: ") %><%= rating.round(4) %> / 5</p> <p id="rating"><%= translate(locale, "Rating: ") %><%= rating.round(4) %> / 5</p>
<p id="Engagement"><%= translate(locale, "Engagement: ") %><%= engagement.round(2) %>%</p> <p id="engagement"><%= translate(locale, "Engagement: ") %><%= engagement.round(2) %>%</p>
<% if video.allowed_regions.size != REGIONS.size %> <% if video.allowed_regions.size != REGIONS.size %>
<p id="AllowedRegions"> <p id="allowed_regions">
<% if video.allowed_regions.size < REGIONS.size / 2 %> <% if video.allowed_regions.size < REGIONS.size / 2 %>
<%= translate(locale, "Whitelisted regions: ") %><%= video.allowed_regions.join(", ") %> <%= translate(locale, "Whitelisted regions: ") %><%= video.allowed_regions.join(", ") %>
<% else %> <% else %>
<%= translate(locale, "Blacklisted regions: ") %><%= (REGIONS.to_a - video.allowed_regions).join(", ") %> <%= translate(locale, "Blacklisted regions: ") %><%= (REGIONS.to_a - video.allowed_regions).join(", ") %>
<% end %> <% end %>
</p> </p>
<% end %> <% end %>
</div> </div>
@ -140,96 +148,100 @@
<h3><%= video.author %></h3> <h3><%= video.author %></h3>
</a> </a>
</p> </p>
<% ucid = video.ucid %> <% ucid = video.ucid %>
<% author = video.author %> <% author = video.author %>
<% sub_count_text = video.sub_count_text %> <% sub_count_text = video.sub_count_text %>
<%= rendered "components/subscribe_widget" %> <%= rendered "components/subscribe_widget" %>
<p> <p>
<b><%= translate(locale, "Shared `x`", video.published.to_s("%B %-d, %Y")) %></b> <b><%= translate(locale, "Shared `x`", video.published.to_s("%B %-d, %Y")) %></b>
</p> </p>
<div> <div>
<%= video.description %> <%= video.description %>
</div> </div>
<hr> <hr>
<div id="comments"> <div id="comments">
<% if nojs %> <% if nojs %>
<%= comment_html %> <%= comment_html %>
<% else %> <% else %>
<noscript> <noscript>
<a href="/watch?<%= env.params.query %>&nojs=1"> <a href="/watch?<%= env.params.query %>&nojs=1">
<%= translate(locale, "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.") %> <%= translate(locale, "Hi! Looks like you have JavaScript turned off. Click here to view comments, keep in mind they may take a bit longer to load.") %>
</a> </a>
</noscript> </noscript>
<% end %> <% end %>
</div> </div>
</div> </div>
</div> </div>
<% if params.related_videos || plid %> <% if params.related_videos || plid %>
<div class="pure-u-1 pure-u-lg-1-5"> <div class="pure-u-1 pure-u-lg-1-5">
<% if plid %> <% if plid %>
<div id="playlist" class="h-box"> <div id="playlist" class="h-box"></div>
</div> <% end %>
<% end %>
<% if params.related_videos %>
<% if params.related_videos %> <div class="h-box">
<div class="h-box"> <% if !rvs.empty? %>
<div <% if plid %>style="display:none"<% end %>>
<% if !rvs.empty? %> <div class="pure-control-group">
<div <% if plid %>style="display:none"<% end %>> <label for="continue"><%= translate(locale, "Autoplay next video: ") %></label>
<div class="pure-control-group"> <input name="continue" onclick="continue_autoplay(this)" id="continue" type="checkbox" <% if params.continue %>checked<% end %>>
<label for="continue"><%= translate(locale, "Autoplay next video: ") %></label> </div>
<input name="continue" onclick="continue_autoplay(this)" id="continue" type="checkbox" <% if params.continue %>checked<% end %>> <hr>
</div> </div>
<hr> <% end %>
</div>
<% end %> <% rvs.each do |rv| %>
<% if rv["id"]? %>
<% rvs.each do |rv| %> <a href="/watch?v=<%= rv["id"] %>">
<% if rv["id"]? %> <% if !env.get("preferences").as(Preferences).thin_mode %>
<a href="/watch?v=<%= rv["id"] %>"> <div class="thumbnail">
<% if env.get("preferences").as(Preferences).thin_mode %> <img class="thumbnail" src="/vi/<%= rv["id"] %>/mqdefault.jpg">
<% else %> <p class="length"><%= recode_length_seconds(rv["length_seconds"]?.try &.to_i? || 0) %></p>
<div class="thumbnail"> </div>
<img class="thumbnail" src="/vi/<%= rv["id"] %>/mqdefault.jpg"> <% end %>
<p class="length"><%= recode_length_seconds(rv["length_seconds"]?.try &.to_i? || 0) %></p> <p style="width:100%"><%= rv["title"] %></p>
</div> <h5 class="pure-g">
<% end %> <div class="pure-u-14-24">
<p style="width:100%"><%= rv["title"] %></p> <b style="width:100%"><%= rv["author"] %></b>
<h5 class="pure-g"> </div>
<div class="pure-u-14-24">
<b style="width:100%"><%= rv["author"] %></b> <div class="pure-u-10-24" style="text-align:right">
</div> <% if views = rv["short_view_count_text"]?.try &.delete(", views") %>
<b class="width:100%"><%= translate(locale, "`x` views", views) %></b>
<div class="pure-u-10-24" style="text-align:right"> <% end %>
<% if views = rv["short_view_count_text"]?.try &.delete(", views") %> </div>
<b class="width:100%"><%= translate(locale, "`x` views", views) %></b> </h5>
<% end %> </a>
</div> <% end %>
</h5> <% end %>
</a> </div>
<% end %> <% end %>
<% end %>
</div> </div>
<% end %>
</div>
<% end %> <% end %>
</div> </div>
<script> <script>
<% if !rvs.empty? && !plid && params.continue %> <% if !rvs.empty? && !plid && params.continue %>
player.on('ended', function() { player.on('ended', function() {
location.assign("/watch?v=" location.assign('/watch?v=' +
+ "<%= rvs.select { |rv| rv["id"]? }[0]?.try &.["id"] %>" '<%= rvs.select { |rv| rv["id"]? }[0]?.try &.["id"] %>' +
+ "&continue=1" '&continue=1' +
<% if params.listen != preferences.listen %> <% if params.listen != preferences.listen %>
+ "&listen=<%= params.listen %>" '&listen=<%= params.listen %>' +
<% end %> <% end %>
<% if params.autoplay || params.continue_autoplay %> <% if params.autoplay || params.continue_autoplay %>
+ "&autoplay=1" '&autoplay=1' +
<% end %> <% end %>
<% if params.speed != preferences.speed %> <% if params.speed != preferences.speed %>
+ "&speed=<%= params.speed %>" '&speed=<%= params.speed %>' +
<% end %> <% end %>
''
); );
}); });
<% end %> <% end %>
@ -237,18 +249,19 @@ player.on('ended', function() {
function continue_autoplay(target) { function continue_autoplay(target) {
if (target.checked) { if (target.checked) {
player.on('ended', function() { player.on('ended', function() {
location.assign("/watch?v=" location.assign('/watch?v=' +
+ "<%= rvs.select { |rv| rv["id"]? }[0]?.try &.["id"] %>" '<%= rvs.select { |rv| rv["id"]? }[0]?.try &.["id"] %>' +
+ "&continue=1" '&continue=1' +
<% if params.listen != preferences.listen %> <% if params.listen != preferences.listen %>
+ "&listen=<%= params.listen %>" '&listen=<%= params.listen %>' +
<% end %> <% end %>
<% if params.autoplay || params.continue_autoplay %> <% if params.autoplay || params.continue_autoplay %>
+ "&autoplay=1" '&autoplay=1' +
<% end %> <% end %>
<% if params.speed != preferences.speed %> <% if params.speed != preferences.speed %>
+ "&speed=<%= params.speed %>" '&speed=<%= params.speed %>' +
<% end %> <% end %>
''
); );
}); });
} else { } else {
@ -257,10 +270,10 @@ function continue_autoplay(target) {
} }
function number_with_separator(val) { function number_with_separator(val) {
while (/(\d+)(\d{3})/.test(val.toString())) { while (/(\d+)(\d{3})/.test(val.toString())) {
val = val.toString().replace(/(\d+)(\d{3})/, "$1" + "," + "$2"); val = val.toString().replace(/(\d+)(\d{3})/, "$1" + "," + "$2");
} }
return val; return val;
} }
<% ucid = video.ucid %> <% ucid = video.ucid %>
@ -269,31 +282,33 @@ function number_with_separator(val) {
<%= rendered "components/subscribe_widget_script" %> <%= rendered "components/subscribe_widget_script" %>
<% if plid %> <% if plid %>
function get_playlist(timeouts = 0) { function get_playlist(plid, timeouts = 0) {
playlist = document.getElementById("playlist"); playlist = document.getElementById('playlist');
if (timeouts > 10) { if (timeouts > 10) {
console.log("Failed to pull playlist"); console.log('Failed to pull playlist');
playlist.innerHTML = ""; playlist.innerHTML = '';
return; return;
} }
playlist.innerHTML = ' \ playlist.innerHTML = ' \
<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3> \ <h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3> \
<hr>' <hr>'
var plid = "<%= plid %>" if (plid.startsWith('RD')) {
var plid_url = '/api/v1/mixes/' + plid +
if (plid.startsWith("RD")) { '?continuation=<%= video.id %>' +
var plid_url = "/api/v1/mixes/<%= plid %>?continuation=<%= video.id %>&format=html&hl=<%= env.get("preferences").as(Preferences).locale %>"; '&format=html&hl=<%= env.get("preferences").as(Preferences).locale %>';
} else { } else {
var plid_url = "/api/v1/playlists/<%= plid %>?continuation=<%= video.id %>&format=html&hl=<%= env.get("preferences").as(Preferences).locale %>"; var plid_url = '/api/v1/playlists/' + plid +
'?continuation=<%= video.id %>' +
'&format=html&hl=<%= env.get("preferences").as(Preferences).locale %>';
} }
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
xhr.responseType = "json"; xhr.responseType = 'json';
xhr.timeout = 20000; xhr.timeout = 20000;
xhr.open("GET", plid_url, true); xhr.open('GET', plid_url, true);
xhr.send(); xhr.send();
xhr.onreadystatechange = function() { xhr.onreadystatechange = function() {
@ -303,228 +318,232 @@ function get_playlist(timeouts = 0) {
if (xhr.response.nextVideo) { if (xhr.response.nextVideo) {
player.on('ended', function() { player.on('ended', function() {
location.assign("/watch?v=" location.assign('/watch?v=' + xhr.response.nextVideo +
+ xhr.response.nextVideo '&list=' + plid +
+ "&list=<%= plid %>"
<% if params.listen != preferences.listen %> <% if params.listen != preferences.listen %>
+ "&listen=<%= params.listen %>" '&listen=<%= params.listen %>' +
<% end %> <% end %>
<% if params.autoplay || params.continue_autoplay %> <% if params.autoplay || params.continue_autoplay %>
+ "&autoplay=1" '&autoplay=1' +
<% end %> <% end %>
<% if params.speed != preferences.speed %> <% if params.speed != preferences.speed %>
+ "&speed=<%= params.speed %>" '&speed=<%= params.speed %>' +
<% end %> <% end %>
''
); );
}); });
} }
} else { } else {
playlist.innerHTML = ""; playlist.innerHTML = '';
document.getElementById('continue').style.display = ""; document.getElementById('continue').style.display = '';
} }
} }
}; };
xhr.ontimeout = function() { xhr.ontimeout = function() {
console.log("Pulling playlist timed out."); console.log('Pulling playlist timed out.');
playlist = document.getElementById('playlist');
playlist = document.getElementById("playlist");
playlist.innerHTML = playlist.innerHTML =
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3><hr>'; '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3><hr>';
get_playlist(timeouts + 1); get_playlist(plid, timeouts + 1);
}; };
} }
get_playlist(); get_playlist('<%= plid %>');
<% end %> <% end %>
function get_reddit_comments(timeouts = 0) { function get_reddit_comments(timeouts = 0) {
comments = document.getElementById("comments"); comments = document.getElementById('comments');
if (timeouts > 10) { if (timeouts > 10) {
console.log("Failed to pull comments"); console.log('Failed to pull comments');
comments.innerHTML = ""; comments.innerHTML = '';
return; return;
}
var fallback = comments.innerHTML;
comments.innerHTML =
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
var url = "/api/v1/comments/<%= video.id %>?source=reddit&format=html&hl=<%= env.get("preferences").as(Preferences).locale %>";
var xhr = new XMLHttpRequest();
xhr.responseType = "json";
xhr.timeout = 20000;
xhr.open("GET", url, true);
xhr.send();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
comments.innerHTML = ' \
<div> \
<h3> \
<a href="javascript:void(0)" onclick="toggle_comments(this)">[ - ]</a> \
{title} \
</h3> \
<p> \
<b> \
<a href="javascript:void(0)" onclick="swap_comments(\'youtube\')"> \
<%= translate(locale, "View YouTube comments") %> \
</a> \
</b> \
</p> \
<b> \
<a rel="noopener" target="_blank" href="https://reddit.com{permalink}"><%= translate(locale, "View more comments on Reddit") %></a> \
</b> \
</div> \
<div>{contentHtml}</div> \
<hr>'.supplant({
title: xhr.response.title,
permalink: xhr.response.permalink,
contentHtml: xhr.response.contentHtml
});
} else {
<% if preferences && preferences.comments[1] == "youtube" %>
get_youtube_comments(timeouts + 1);
<% else %>
comments.innerHTML = fallback;
<% end %>
}
} }
};
xhr.ontimeout = function() { var fallback = comments.innerHTML;
console.log("Pulling comments timed out."); comments.innerHTML =
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
get_reddit_comments(timeouts + 1); var url = '/api/v1/comments/<%= video.id %>' +
}; '?source=reddit&format=html' +
} '&hl=<%= env.get("preferences").as(Preferences).locale %>';
var xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.timeout = 20000;
xhr.open('GET', url, true);
xhr.send();
function get_youtube_comments(timeouts = 0) { xhr.onreadystatechange = function() {
comments = document.getElementById("comments"); if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (timeouts > 10) { comments.innerHTML = ' \
console.log("Failed to pull comments");
comments.innerHTML = "";
return;
}
var fallback = comments.innerHTML;
comments.innerHTML =
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
var url = "/api/v1/comments/<%= video.id %>?format=html&hl=<%= env.get("preferences").as(Preferences).locale %>&thin_mode=<%= env.get("preferences").as(Preferences).thin_mode %>";
var xhr = new XMLHttpRequest();
xhr.responseType = "json";
xhr.timeout = 20000;
xhr.open("GET", url, true);
xhr.send();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (xhr.response.commentCount > 0) {
comments.innerHTML = ' \
<div> \ <div> \
<h3> \ <h3> \
<a href="javascript:void(0)" onclick="toggle_comments(this)">[ - ]</a> \ <a href="javascript:void(0)" onclick="toggle_comments(this)">[ - ]</a> \
<%= translate(locale, "View `x` comments", "{commentCount}") %> \ {title} \
</h3> \ </h3> \
<p> \
<b> \
<a href="javascript:void(0)" onclick="swap_comments(\'youtube\')"> \
<%= translate(locale, "View YouTube comments") %> \
</a> \
</b> \
</p> \
<b> \ <b> \
<a href="javascript:void(0)" onclick="swap_comments(\'reddit\')"> \ <a rel="noopener" target="_blank" href="https://reddit.com{permalink}"><%= translate(locale, "View more comments on Reddit") %></a> \
<%= translate(locale, "View Reddit comments") %> \
</a> \
</b> \ </b> \
</div> \ </div> \
<div>{contentHtml}</div> \ <div>{contentHtml}</div> \
<hr>'.supplant({ <hr>'.supplant({
contentHtml: xhr.response.contentHtml, title: xhr.response.title,
commentCount: number_with_separator(xhr.response.commentCount) permalink: xhr.response.permalink,
}); contentHtml: xhr.response.contentHtml
} else { });
comments.innerHTML = ""; } else {
<% if preferences && preferences.comments[1] == "youtube" %>
get_youtube_comments(timeouts + 1);
<% else %>
comments.innerHTML = fallback;
<% end %>
}
} }
} else { };
<% if preferences && preferences.comments[1] == "youtube" %>
get_youtube_comments(timeouts + 1); xhr.ontimeout = function() {
<% else %> console.log('Pulling comments timed out.');
comments.innerHTML = ""; get_reddit_comments(timeouts + 1);
<% end %> };
} }
function get_youtube_comments(timeouts = 0) {
comments = document.getElementById('comments');
if (timeouts > 10) {
console.log('Failed to pull comments');
comments.innerHTML = '';
return;
} }
};
xhr.ontimeout = function() {
console.log("Pulling comments timed out.");
var fallback = comments.innerHTML;
comments.innerHTML = comments.innerHTML =
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>'; '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
get_youtube_comments(timeouts + 1);
}; var url = '/api/v1/comments/<%= video.id %>' +
'?format=html' +
'&hl=<%= env.get("preferences").as(Preferences).locale %>' +
'&thin_mode=<%= env.get("preferences").as(Preferences).thin_mode %>';
var xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.timeout = 20000;
xhr.open('GET', url, true);
xhr.send();
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
if (xhr.response.commentCount > 0) {
comments.innerHTML = ' \
<div> \
<h3> \
<a href="javascript:void(0)" onclick="toggle_comments(this)">[ - ]</a> \
<%= translate(locale, "View `x` comments", "{commentCount}") %> \
</h3> \
<b> \
<a href="javascript:void(0)" onclick="swap_comments(\'reddit\')"> \
<%= translate(locale, "View Reddit comments") %> \
</a> \
</b> \
</div> \
<div>{contentHtml}</div> \
<hr>'.supplant({
contentHtml: xhr.response.contentHtml,
commentCount: number_with_separator(xhr.response.commentCount)
});
} else {
comments.innerHTML = "";
}
} else {
<% if preferences && preferences.comments[1] == "youtube" %>
get_youtube_comments(timeouts + 1);
<% else %>
comments.innerHTML = '';
<% end %>
}
}
};
xhr.ontimeout = function() {
console.log('Pulling comments timed out.');
comments.innerHTML =
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
get_youtube_comments(timeouts + 1);
};
} }
function get_youtube_replies(target, load_more) { function get_youtube_replies(target, load_more) {
var continuation = target.getAttribute('data-continuation'); var continuation = target.getAttribute('data-continuation');
var body = target.parentNode.parentNode; var body = target.parentNode.parentNode;
var fallback = body.innerHTML; var fallback = body.innerHTML;
body.innerHTML = body.innerHTML =
'<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>'; '<h3 style="text-align:center"><div class="loading"><i class="icon ion-ios-refresh"></i></div></h3>';
var url = '/api/v1/comments/<%= video.id %>?format=html&hl=<%= env.get("preferences").as(Preferences).locale %>&thin_mode=<%= env.get("preferences").as(Preferences).thin_mode %>&continuation=' + var url = '/api/v1/comments/<%= video.id %>' +
continuation; '?format=html' +
var xhr = new XMLHttpRequest(); '&hl=<%= env.get("preferences").as(Preferences).locale %>' +
xhr.responseType = 'json'; '&thin_mode=<%= env.get("preferences").as(Preferences).thin_mode %>' +
xhr.timeout = 20000; '&continuation=' + continuation;
xhr.open('GET', url, true); var xhr = new XMLHttpRequest();
xhr.send(); xhr.responseType = 'json';
xhr.timeout = 20000;
xhr.open('GET', url, true);
xhr.send();
xhr.onreadystatechange = function() { xhr.onreadystatechange = function() {
if (xhr.readyState == 4) { if (xhr.readyState == 4) {
if (xhr.status == 200) { if (xhr.status == 200) {
if (load_more) { if (load_more) {
body = body.parentNode.parentNode; body = body.parentNode.parentNode;
body.removeChild(body.lastElementChild); body.removeChild(body.lastElementChild);
body.innerHTML += xhr.response.contentHtml; body.innerHTML += xhr.response.contentHtml;
} else { } else {
body.innerHTML = ' \ body.innerHTML = ' \
<p><a href="javascript:void(0)" \ <p><a href="javascript:void(0)" \
onclick="hide_youtube_replies(this, \'<%= translate(locale, "Hide replies") %>\', \'<%= translate(locale, "Show replies") %>\')"><%= translate(locale, "Hide replies") %> \ onclick="hide_youtube_replies(this, \'<%= translate(locale, "Hide replies") %>\', \'<%= translate(locale, "Show replies") %>\')"><%= translate(locale, "Hide replies") %> \
</a></p> \ </a></p> \
<div>{contentHtml}</div>'.supplant({ <div>{contentHtml}</div>'.supplant({
contentHtml: xhr.response.contentHtml, contentHtml: xhr.response.contentHtml,
}); });
}
} else {
body.innerHTML = fallback;
}
} }
} else { };
xhr.ontimeout = function() {
console.log('Pulling comments timed out.');
body.innerHTML = fallback; body.innerHTML = fallback;
} };
}
};
xhr.ontimeout = function() {
console.log('Pulling comments timed out.');
body.innerHTML = fallback;
};
} }
<% if preferences %> <% if preferences %>
<% if preferences.comments[0] == "youtube" %> <% if preferences.comments[0] == "youtube" %>
get_youtube_comments(); get_youtube_comments();
<% elsif preferences.comments[0] == "reddit" %> <% elsif preferences.comments[0] == "reddit" %>
get_reddit_comments(); get_reddit_comments();
<% else %> <% else %>
<% if preferences.comments[1] == "youtube" %> <% if preferences.comments[1] == "youtube" %>
get_youtube_comments(); get_youtube_comments();
<% elsif preferences.comments[1] == "reddit" %> <% elsif preferences.comments[1] == "reddit" %>
get_reddit_comments(); get_reddit_comments();
<% else %> <% else %>
comments = document.getElementById("comments"); comments = document.getElementById('comments');
comments.innerHTML = ""; comments.innerHTML = '';
<% end %> <% end %>
<% end %> <% end %>
<% else %> <% else %>
get_youtube_comments(); get_youtube_comments();
<% end %> <% end %>
</script> </script>