mirror of
https://github.com/iv-org/invidious.git
synced 2024-11-25 06:57:22 +00:00
UI: Nicer buttons (#3763)
This commit is contained in:
commit
c8ade5194b
@ -1,3 +1,7 @@
|
||||
/*
|
||||
* Common attributes
|
||||
*/
|
||||
|
||||
html,
|
||||
body {
|
||||
font-family: BlinkMacSystemFont, -apple-system, "Segoe UI", Roboto, Oxygen,
|
||||
@ -11,6 +15,16 @@ body {
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.h-box {
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.v-box {
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
.deleted {
|
||||
background-color: rgb(255, 0, 0, 0.5);
|
||||
}
|
||||
@ -20,6 +34,34 @@ body {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0.5em 0 1em 0;
|
||||
}
|
||||
|
||||
/* A flex container */
|
||||
.flexible {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.flex-left {
|
||||
display: flex;
|
||||
flex: 1 1 auto;
|
||||
flex-flow: row wrap;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
.flex-right {
|
||||
display: flex;
|
||||
flex: 2 0 auto;
|
||||
flex-flow: row nowrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Channel page
|
||||
*/
|
||||
|
||||
.channel-profile > * {
|
||||
font-size: 1.17em;
|
||||
font-weight: bold;
|
||||
@ -90,16 +132,6 @@ body a.channel-owner {
|
||||
}
|
||||
}
|
||||
|
||||
.h-box {
|
||||
padding-left: 1em;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
.v-box {
|
||||
padding-top: 1em;
|
||||
padding-bottom: 1em;
|
||||
}
|
||||
|
||||
div {
|
||||
overflow-wrap: break-word;
|
||||
word-wrap: break-word;
|
||||
@ -115,6 +147,11 @@ div {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Buttons
|
||||
*/
|
||||
|
||||
body a.pure-button {
|
||||
color: rgba(0,0,0,.8);
|
||||
}
|
||||
@ -127,30 +164,48 @@ body a.pure-button-primary,
|
||||
color: rgba(35, 35, 35, 1);
|
||||
}
|
||||
|
||||
button.pure-button-primary:hover,
|
||||
body a.pure-button-primary:hover,
|
||||
button.pure-button-primary:focus,
|
||||
body a.pure-button-primary:focus {
|
||||
background-color: rgba(0, 182, 240, 1);
|
||||
color: #fff;
|
||||
.pure-button-primary,
|
||||
.pure-button-secondary {
|
||||
border: 1px solid #a0a0a0;
|
||||
border-radius: 3px;
|
||||
margin: 0 .4em;
|
||||
}
|
||||
|
||||
.pure-button-secondary.low-profile {
|
||||
padding: 5px 10px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Has to be combined with flex-left/right */
|
||||
.button-container {
|
||||
flex-flow: wrap;
|
||||
gap: 0.5em 0.75em;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Video thumbnails
|
||||
*/
|
||||
|
||||
div.thumbnail {
|
||||
padding: 28.125%;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
img.thumbnail {
|
||||
position: absolute;
|
||||
display: block; /* See: https://stackoverflow.com/a/11635197 */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 0;
|
||||
top: 0;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.thumbnail-placeholder {
|
||||
min-height: 50px;
|
||||
border: 2px dotted;
|
||||
}
|
||||
|
||||
div.watched-overlay {
|
||||
z-index: 50;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -168,30 +223,31 @@ div.watched-indicator {
|
||||
background-color: red;
|
||||
}
|
||||
|
||||
.length {
|
||||
div.thumbnail > .top-left-overlay,
|
||||
div.thumbnail > .bottom-right-overlay {
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
background-color: rgba(35, 35, 35, 0.75);
|
||||
color: #fff;
|
||||
border-radius: 2px;
|
||||
padding: 2px;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
right: 0.25em;
|
||||
bottom: -0.75em;
|
||||
}
|
||||
|
||||
.watched {
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
background-color: rgba(35, 35, 35, 0.75);
|
||||
.top-left-overlay { top: 0.6em; left: 0.6em; }
|
||||
.bottom-right-overlay { bottom: 0.6em; right: 0.6em; }
|
||||
|
||||
.length {
|
||||
padding: 1px;
|
||||
margin: -2px 0;
|
||||
color: #fff;
|
||||
border-radius: 2px;
|
||||
padding: 4px 8px 4px 8px;
|
||||
font-size: 16px;
|
||||
left: 0.2em;
|
||||
top: -0.7em;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.length, .top-left-overlay button {
|
||||
color: #eee;
|
||||
background-color: rgba(35, 35, 35, 0.85) !important;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Navbar
|
||||
*/
|
||||
@ -267,6 +323,11 @@ input[type="search"]::-webkit-search-cancel-button {
|
||||
margin-right: 1em;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Responsive rules
|
||||
*/
|
||||
|
||||
@media only screen and (max-aspect-ratio: 16/9) {
|
||||
.player-dimensions.vjs-fluid {
|
||||
padding-top: 46.86% !important;
|
||||
@ -285,20 +346,28 @@ input[type="search"]::-webkit-search-cancel-button {
|
||||
.navbar > div {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.navbar > div:not(:last-child) {
|
||||
margin-bottom: 1em;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.navbar > .searchbar > form {
|
||||
width: 60%;
|
||||
width: 75%;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 1.25em;
|
||||
margin: 0.42em 0;
|
||||
}
|
||||
|
||||
/* Space out the subscribe & RSS buttons and align them to the left */
|
||||
.title.flexible { display: block; }
|
||||
.title.flexible > .flex-right { margin: 0.75em 0; justify-content: flex-start; }
|
||||
|
||||
/* Space out buttons to make them easier to tap */
|
||||
.user-field { font-size: 125%; }
|
||||
.user-field > :not(:last-child) { margin-right: 1.75em; }
|
||||
|
||||
.icon-buttons { font-size: 125%; }
|
||||
.icon-buttons > :not(:last-child) { margin-right: 0.75em; }
|
||||
}
|
||||
|
||||
@media screen and (max-width: 320px) {
|
||||
@ -315,10 +384,6 @@ input[type="search"]::-webkit-search-cancel-button {
|
||||
|
||||
.video-card-row { margin: 15px 0; }
|
||||
|
||||
.flexible { display: flex; }
|
||||
.flex-left { flex: 1 1 100%; flex-wrap: wrap; }
|
||||
.flex-right { flex: 1 0 auto; flex-wrap: nowrap; }
|
||||
|
||||
p.channel-name { margin: 0; }
|
||||
p.video-data { margin: 0; font-weight: bold; font-size: 80%; }
|
||||
|
||||
@ -347,6 +412,22 @@ p.video-data { margin: 0; font-weight: bold; font-size: 80%; }
|
||||
border: none;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Page navigation
|
||||
*/
|
||||
|
||||
.page-nav-container { margin: 15px 0 30px 0; }
|
||||
|
||||
.page-prev-container { text-align: start; }
|
||||
.page-next-container { text-align: end; }
|
||||
|
||||
.page-prev-container,
|
||||
.page-next-container {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Footer
|
||||
*/
|
||||
@ -389,6 +470,7 @@ span > select {
|
||||
word-wrap: normal;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Light theme
|
||||
*/
|
||||
@ -401,9 +483,18 @@ span > select {
|
||||
color: #075A9E !important;
|
||||
}
|
||||
|
||||
.light-theme a.pure-button-primary:hover,
|
||||
.light-theme a.pure-button-primary:focus {
|
||||
.light-theme .pure-button-primary:hover,
|
||||
.light-theme .pure-button-primary:focus,
|
||||
.light-theme .pure-button-secondary:hover,
|
||||
.light-theme .pure-button-secondary:focus {
|
||||
color: #fff !important;
|
||||
border-color: rgba(0, 182, 240, 0.75) !important;
|
||||
background-color: rgba(0, 182, 240, 0.75) !important;
|
||||
}
|
||||
|
||||
.light-theme .pure-button-secondary:not(.low-profile) {
|
||||
color: #335d7a;
|
||||
background-color: #fff2;
|
||||
}
|
||||
|
||||
.light-theme a {
|
||||
@ -431,9 +522,18 @@ span > select {
|
||||
color: #075A9E !important;
|
||||
}
|
||||
|
||||
.no-theme a.pure-button-primary:hover,
|
||||
.no-theme a.pure-button-primary:focus {
|
||||
.no-theme .pure-button-primary:hover,
|
||||
.no-theme .pure-button-primary:focus,
|
||||
.no-theme .pure-button-secondary:hover,
|
||||
.no-theme .pure-button-secondary:focus {
|
||||
color: #fff !important;
|
||||
border-color: rgba(0, 182, 240, 0.75) !important;
|
||||
background-color: rgba(0, 182, 240, 0.75) !important;
|
||||
}
|
||||
|
||||
.no-theme .pure-button-secondary:not(.low-profile) {
|
||||
color: #335d7a;
|
||||
background-color: #fff2;
|
||||
}
|
||||
|
||||
.no-theme a {
|
||||
@ -453,6 +553,7 @@ span > select {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Dark theme
|
||||
*/
|
||||
@ -465,6 +566,20 @@ span > select {
|
||||
color: rgb(0, 182, 240);
|
||||
}
|
||||
|
||||
.dark-theme .pure-button-primary:hover,
|
||||
.dark-theme .pure-button-primary:focus,
|
||||
.dark-theme .pure-button-secondary:hover,
|
||||
.dark-theme .pure-button-secondary:focus {
|
||||
color: #fff !important;
|
||||
border-color: rgb(0, 182, 240) !important;
|
||||
background-color: rgba(0, 182, 240, 1) !important;
|
||||
}
|
||||
|
||||
.dark-theme .pure-button-secondary {
|
||||
background-color: #0002;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.dark-theme a {
|
||||
color: #a0a0a0;
|
||||
text-decoration: none;
|
||||
@ -505,6 +620,20 @@ body.dark-theme {
|
||||
color: rgb(0, 182, 240);
|
||||
}
|
||||
|
||||
.no-theme .pure-button-primary:hover,
|
||||
.no-theme .pure-button-primary:focus,
|
||||
.no-theme .pure-button-secondary:hover,
|
||||
.no-theme .pure-button-secondary:focus {
|
||||
color: #fff !important;
|
||||
border-color: rgb(0, 182, 240) !important;
|
||||
background-color: rgba(0, 182, 240, 1) !important;
|
||||
}
|
||||
|
||||
.no-theme .pure-button-secondary {
|
||||
background-color: #0002;
|
||||
color: #ddd;
|
||||
}
|
||||
|
||||
.no-theme a {
|
||||
color: #a0a0a0;
|
||||
text-decoration: none;
|
||||
@ -539,6 +668,12 @@ body.dark-theme {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Miscellanous
|
||||
*/
|
||||
|
||||
|
||||
/*With commit d9528f5 all contents of the page is now within a flexbox. However,
|
||||
the hr element is rendered improperly within one.
|
||||
See https://stackoverflow.com/a/34372979 for more info */
|
||||
@ -576,12 +711,7 @@ label[for="music-desc-expansion"]:hover {
|
||||
}
|
||||
|
||||
/* Bidi (bidirectional text) support */
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
p,
|
||||
h1, h2, h3, h4, h5, p,
|
||||
#descriptionWrapper,
|
||||
#description-box,
|
||||
#music-description-box {
|
||||
|
@ -9,6 +9,11 @@
|
||||
"generic_subscribers_count_plural": "{{count}} subscribers",
|
||||
"generic_subscriptions_count": "{{count}} subscription",
|
||||
"generic_subscriptions_count_plural": "{{count}} subscriptions",
|
||||
"generic_button_delete": "Delete",
|
||||
"generic_button_edit": "Edit",
|
||||
"generic_button_save": "Save",
|
||||
"generic_button_cancel": "Cancel",
|
||||
"generic_button_rss": "RSS",
|
||||
"LIVE": "LIVE",
|
||||
"Shared `x` ago": "Shared `x` ago",
|
||||
"Unsubscribe": "Unsubscribe",
|
||||
@ -170,6 +175,7 @@
|
||||
"Title": "Title",
|
||||
"Playlist privacy": "Playlist privacy",
|
||||
"Editing playlist `x`": "Editing playlist `x`",
|
||||
"playlist_button_add_items": "Add videos",
|
||||
"Show more": "Show more",
|
||||
"Show less": "Show less",
|
||||
"Watch on YouTube": "Watch on YouTube",
|
||||
|
@ -9,6 +9,11 @@
|
||||
"generic_subscribers_count_plural": "{{count}} abonnés",
|
||||
"generic_subscriptions_count": "{{count}} abonnement",
|
||||
"generic_subscriptions_count_plural": "{{count}} abonnements",
|
||||
"generic_button_delete": "Supprimer",
|
||||
"generic_button_edit": "Editer",
|
||||
"generic_button_save": "Enregistrer",
|
||||
"generic_button_cancel": "Annuler",
|
||||
"generic_button_rss": "RSS",
|
||||
"LIVE": "EN DIRECT",
|
||||
"Shared `x` ago": "Ajoutée il y a `x`",
|
||||
"Unsubscribe": "Se désabonner",
|
||||
@ -149,6 +154,7 @@
|
||||
"Title": "Titre",
|
||||
"Playlist privacy": "Paramètres de confidentialité de la liste de lecture",
|
||||
"Editing playlist `x`": "Modifier la liste de lecture `x`",
|
||||
"playlist_button_add_items": "Ajouter des vidéos",
|
||||
"Show more": "Afficher plus",
|
||||
"Show less": "Afficher moins",
|
||||
"Watch on YouTube": "Voir la vidéo sur Youtube",
|
||||
|
97
src/invidious/frontend/pagination.cr
Normal file
97
src/invidious/frontend/pagination.cr
Normal file
@ -0,0 +1,97 @@
|
||||
require "uri"
|
||||
|
||||
module Invidious::Frontend::Pagination
|
||||
extend self
|
||||
|
||||
private def previous_page(str : String::Builder, locale : String?, url : String)
|
||||
# Link
|
||||
str << %(<a href=") << url << %(" class="pure-button pure-button-secondary">)
|
||||
|
||||
if locale_is_rtl?(locale)
|
||||
# Inverted arrow ("previous" points to the right)
|
||||
str << translate(locale, "Previous page")
|
||||
str << " "
|
||||
str << %(<i class="icon ion-ios-arrow-forward"></i>)
|
||||
else
|
||||
# Regular arrow ("previous" points to the left)
|
||||
str << %(<i class="icon ion-ios-arrow-back"></i>)
|
||||
str << " "
|
||||
str << translate(locale, "Previous page")
|
||||
end
|
||||
|
||||
str << "</a>"
|
||||
end
|
||||
|
||||
private def next_page(str : String::Builder, locale : String?, url : String)
|
||||
# Link
|
||||
str << %(<a href=") << url << %(" class="pure-button pure-button-secondary">)
|
||||
|
||||
if locale_is_rtl?(locale)
|
||||
# Inverted arrow ("next" points to the left)
|
||||
str << %(<i class="icon ion-ios-arrow-back"></i>)
|
||||
str << " "
|
||||
str << translate(locale, "Next page")
|
||||
else
|
||||
# Regular arrow ("next" points to the right)
|
||||
str << translate(locale, "Next page")
|
||||
str << " "
|
||||
str << %(<i class="icon ion-ios-arrow-forward"></i>)
|
||||
end
|
||||
|
||||
str << "</a>"
|
||||
end
|
||||
|
||||
def nav_numeric(locale : String?, *, base_url : String | URI, current_page : Int, show_next : Bool = true)
|
||||
return String.build do |str|
|
||||
str << %(<div class="h-box">\n)
|
||||
str << %(<div class="page-nav-container flexible">\n)
|
||||
|
||||
str << %(<div class="page-prev-container flex-left">)
|
||||
|
||||
if current_page > 1
|
||||
params_prev = URI::Params{"page" => (current_page - 1).to_s}
|
||||
url_prev = HttpServer::Utils.add_params_to_url(base_url, params_prev)
|
||||
|
||||
self.previous_page(str, locale, url_prev.to_s)
|
||||
end
|
||||
|
||||
str << %(</div>\n)
|
||||
str << %(<div class="page-next-container flex-right">)
|
||||
|
||||
if show_next
|
||||
params_next = URI::Params{"page" => (current_page + 1).to_s}
|
||||
url_next = HttpServer::Utils.add_params_to_url(base_url, params_next)
|
||||
|
||||
self.next_page(str, locale, url_next.to_s)
|
||||
end
|
||||
|
||||
str << %(</div>\n)
|
||||
|
||||
str << %(</div>\n)
|
||||
str << %(</div>\n\n)
|
||||
end
|
||||
end
|
||||
|
||||
def nav_ctoken(locale : String?, *, base_url : String | URI, ctoken : String?)
|
||||
return String.build do |str|
|
||||
str << %(<div class="h-box">\n)
|
||||
str << %(<div class="page-nav-container flexible">\n)
|
||||
|
||||
str << %(<div class="page-prev-container flex-left"></div>\n)
|
||||
|
||||
str << %(<div class="page-next-container flex-right">)
|
||||
|
||||
if !ctoken.nil?
|
||||
params_next = URI::Params{"continuation" => ctoken}
|
||||
url_next = HttpServer::Utils.add_params_to_url(base_url, params_next)
|
||||
|
||||
self.next_page(str, locale, url_next.to_s)
|
||||
end
|
||||
|
||||
str << %(</div>\n)
|
||||
|
||||
str << %(</div>\n)
|
||||
str << %(</div>\n\n)
|
||||
end
|
||||
end
|
||||
end
|
@ -165,3 +165,12 @@ def translate_bool(locale : String?, translation : Bool)
|
||||
return translate(locale, "No")
|
||||
end
|
||||
end
|
||||
|
||||
def locale_is_rtl?(locale : String?)
|
||||
# Fallback to en-US
|
||||
return false if locale.nil?
|
||||
|
||||
# Arabic, Persian, Hebrew
|
||||
# See https://en.wikipedia.org/wiki/Right-to-left_script#List_of_RTL_scripts
|
||||
return {"ar", "fa", "he"}.includes? locale
|
||||
end
|
||||
|
@ -1,3 +1,5 @@
|
||||
require "uri"
|
||||
|
||||
module Invidious::HttpServer
|
||||
module Utils
|
||||
extend self
|
||||
@ -16,5 +18,23 @@ module Invidious::HttpServer
|
||||
return "#{url.request_target}?#{params}"
|
||||
end
|
||||
end
|
||||
|
||||
def add_params_to_url(url : String | URI, params : URI::Params) : URI
|
||||
url = URI.parse(url) if url.is_a?(String)
|
||||
|
||||
url_query = url.query || ""
|
||||
|
||||
# Append the parameters
|
||||
url.query = String.build do |str|
|
||||
if !url_query.empty?
|
||||
str << url_query
|
||||
str << '&'
|
||||
end
|
||||
|
||||
str << params
|
||||
end
|
||||
|
||||
return url
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -102,6 +102,10 @@ module Invidious::Routes::Feeds
|
||||
end
|
||||
env.set "user", user
|
||||
|
||||
# Used for pagination links
|
||||
base_url = "/feed/subscriptions"
|
||||
base_url += "?max_results=#{max_results}" if env.params.query.has_key?("max_results")
|
||||
|
||||
templated "feeds/subscriptions"
|
||||
end
|
||||
|
||||
@ -129,6 +133,10 @@ module Invidious::Routes::Feeds
|
||||
end
|
||||
watched ||= [] of String
|
||||
|
||||
# Used for pagination links
|
||||
base_url = "/feed/history"
|
||||
base_url += "?max_results=#{max_results}" if env.params.query.has_key?("max_results")
|
||||
|
||||
templated "feeds/history"
|
||||
end
|
||||
|
||||
|
@ -163,13 +163,20 @@ module Invidious::Routes::Playlists
|
||||
end
|
||||
|
||||
begin
|
||||
videos = get_playlist_videos(playlist, offset: (page - 1) * 100)
|
||||
items = get_playlist_videos(playlist, offset: (page - 1) * 100)
|
||||
rescue ex
|
||||
videos = [] of PlaylistVideo
|
||||
items = [] of PlaylistVideo
|
||||
end
|
||||
|
||||
csrf_token = generate_response(sid, {":edit_playlist"}, HMAC_KEY)
|
||||
|
||||
# Pagination
|
||||
page_nav_html = Frontend::Pagination.nav_numeric(locale,
|
||||
base_url: "/playlist?list=#{playlist.id}",
|
||||
current_page: page,
|
||||
show_next: (items.size == 100)
|
||||
)
|
||||
|
||||
templated "edit_playlist"
|
||||
end
|
||||
|
||||
@ -247,11 +254,19 @@ module Invidious::Routes::Playlists
|
||||
|
||||
begin
|
||||
query = Invidious::Search::Query.new(env.params.query, :playlist, region)
|
||||
videos = query.process.select(SearchVideo).map(&.as(SearchVideo))
|
||||
items = query.process.select(SearchVideo).map(&.as(SearchVideo))
|
||||
rescue ex
|
||||
videos = [] of SearchVideo
|
||||
items = [] of SearchVideo
|
||||
end
|
||||
|
||||
# Pagination
|
||||
query_encoded = URI.encode_www_form(query.try &.text || "", space_to_plus: true)
|
||||
page_nav_html = Frontend::Pagination.nav_numeric(locale,
|
||||
base_url: "/add_playlist_items?list=#{playlist.id}&q=#{query_encoded}",
|
||||
current_page: page,
|
||||
show_next: (items.size >= 20)
|
||||
)
|
||||
|
||||
env.set "add_playlist_items", plid
|
||||
templated "add_playlist_items"
|
||||
end
|
||||
@ -418,7 +433,7 @@ module Invidious::Routes::Playlists
|
||||
end
|
||||
|
||||
begin
|
||||
videos = get_playlist_videos(playlist, offset: (page - 1) * 200)
|
||||
items = get_playlist_videos(playlist, offset: (page - 1) * 200)
|
||||
rescue ex
|
||||
return error_template(500, "Error encountered while retrieving playlist videos.<br>#{ex.message}")
|
||||
end
|
||||
@ -427,6 +442,13 @@ module Invidious::Routes::Playlists
|
||||
env.set "remove_playlist_items", plid
|
||||
end
|
||||
|
||||
# Pagination
|
||||
page_nav_html = Frontend::Pagination.nav_numeric(locale,
|
||||
base_url: "/playlist?list=#{playlist.id}",
|
||||
current_page: page,
|
||||
show_next: (page_count != 1 && page < page_count)
|
||||
)
|
||||
|
||||
templated "playlist"
|
||||
end
|
||||
|
||||
|
@ -52,24 +52,28 @@ module Invidious::Routes::Search
|
||||
user = env.get? "user"
|
||||
|
||||
begin
|
||||
videos = query.process
|
||||
items = query.process
|
||||
rescue ex : ChannelSearchException
|
||||
return error_template(404, "Unable to find channel with id of '#{HTML.escape(ex.channel)}'. Are you sure that's an actual channel id? It should look like 'UC4QobU6STFB0P71PMvOGN5A'.")
|
||||
rescue ex
|
||||
return error_template(500, ex)
|
||||
end
|
||||
|
||||
params = query.to_http_params
|
||||
url_prev_page = "/search?#{params}&page=#{query.page - 1}"
|
||||
url_next_page = "/search?#{params}&page=#{query.page + 1}"
|
||||
|
||||
redirect_url = Invidious::Frontend::Misc.redirect_url(env)
|
||||
|
||||
# Pagination
|
||||
page_nav_html = Frontend::Pagination.nav_numeric(locale,
|
||||
base_url: "/search?#{query.to_http_params}",
|
||||
current_page: query.page,
|
||||
show_next: (items.size >= 20)
|
||||
)
|
||||
|
||||
if query.type == Invidious::Search::Query::Type::Channel
|
||||
env.set "search", "channel:#{query.channel} #{query.text}"
|
||||
else
|
||||
env.set "search", query.text
|
||||
end
|
||||
|
||||
templated "search"
|
||||
end
|
||||
end
|
||||
@ -91,16 +95,18 @@ module Invidious::Routes::Search
|
||||
end
|
||||
|
||||
begin
|
||||
videos = Invidious::Hashtag.fetch(hashtag, page)
|
||||
items = Invidious::Hashtag.fetch(hashtag, page)
|
||||
rescue ex
|
||||
return error_template(500, ex)
|
||||
end
|
||||
|
||||
params = env.params.query.empty? ? "" : "&#{env.params.query}"
|
||||
|
||||
# Pagination
|
||||
hashtag_encoded = URI.encode_www_form(hashtag, space_to_plus: false)
|
||||
url_prev_page = "/hashtag/#{hashtag_encoded}?page=#{page - 1}#{params}"
|
||||
url_next_page = "/hashtag/#{hashtag_encoded}?page=#{page + 1}#{params}"
|
||||
page_nav_html = Frontend::Pagination.nav_numeric(locale,
|
||||
base_url: "/hashtag/#{hashtag_encoded}",
|
||||
current_page: page,
|
||||
show_next: (items.size >= 60)
|
||||
)
|
||||
|
||||
templated "hashtag"
|
||||
end
|
||||
|
@ -31,33 +31,5 @@
|
||||
</script>
|
||||
<script src="/js/playlist_widget.js?v=<%= ASSET_COMMIT %>"></script>
|
||||
|
||||
<div class="pure-g">
|
||||
<% videos.each_slice(4) do |slice| %>
|
||||
<% slice.each do |item| %>
|
||||
<%= rendered "components/item" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<script src="/js/watched_indicator.js"></script>
|
||||
|
||||
<% if query %>
|
||||
<%- query_encoded = URI.encode_www_form(query.text, space_to_plus: true) -%>
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-1 pure-u-lg-1-5">
|
||||
<% if query.page > 1 %>
|
||||
<a href="/add_playlist_items?list=<%= plid %>&q=<%= query_encoded %>&page=<%= page - 1 %>">
|
||||
<%= translate(locale, "Previous page") %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-lg-3-5"></div>
|
||||
<div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
|
||||
<% if videos.size >= 20 %>
|
||||
<a href="/add_playlist_items?list=<%= plid %>&q=<%= query_encoded %>&page=<%= page + 1 %>">
|
||||
<%= translate(locale, "Next page") %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= rendered "components/items_paginated" %>
|
||||
|
@ -17,7 +17,12 @@
|
||||
|
||||
youtube_url = "https://www.youtube.com#{relative_url}"
|
||||
redirect_url = Invidious::Frontend::Misc.redirect_url(env)
|
||||
-%>
|
||||
|
||||
page_nav_html = IV::Frontend::Pagination.nav_ctoken(locale,
|
||||
base_url: relative_url,
|
||||
ctoken: next_continuation
|
||||
)
|
||||
%>
|
||||
|
||||
<% content_for "header" do %>
|
||||
<%- if selected_tab.videos? -%>
|
||||
@ -45,21 +50,5 @@
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div class="pure-g">
|
||||
<% items.each do |item| %>
|
||||
<%= rendered "components/item" %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<script src="/js/watched_indicator.js"></script>
|
||||
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-1 pure-u-md-4-5"></div>
|
||||
<div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
|
||||
<% if next_continuation %>
|
||||
<a href="<%= relative_url %>?continuation=<%= next_continuation %><% if sort_options.any? sort_by %>&sort_by=<%= sort_by %><% end %>">
|
||||
<%= translate(locale, "Next page") %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<%= rendered "components/items_paginated" %>
|
||||
|
@ -8,29 +8,30 @@
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-2-3">
|
||||
<div class="pure-g h-box flexible title">
|
||||
<div class="pure-u-1-2 flex-left flexible">
|
||||
<div class="channel-profile">
|
||||
<img src="/ggpht<%= channel_profile_pic %>" alt="" />
|
||||
<span><%= author %></span><% if !channel.verified.nil? && channel.verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pure-u-1-3">
|
||||
<h3 style="text-align:right">
|
||||
<a href="/feed/channel/<%= ucid %>"><i class="icon ion-logo-rss"></i></a>
|
||||
</h3>
|
||||
|
||||
<div class="pure-u-1-2 flex-right flexible button-container">
|
||||
<div class="pure-u">
|
||||
<% sub_count_text = number_to_short_text(channel.sub_count) %>
|
||||
<%= rendered "components/subscribe_widget" %>
|
||||
</div>
|
||||
|
||||
<div class="pure-u">
|
||||
<a class="pure-button pure-button-secondary" dir="auto" href="/feed/channel/<%= ucid %>">
|
||||
<i class="icon ion-logo-rss"></i> <%= translate(locale, "generic_button_rss") %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-box">
|
||||
<div id="descriptionWrapper">
|
||||
<p><span style="white-space:pre-wrap"><%= channel.description_html %></span></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-box">
|
||||
<% sub_count_text = number_to_short_text(channel.sub_count) %>
|
||||
<%= rendered "components/subscribe_widget" %>
|
||||
<div id="descriptionWrapper"><p><span style="white-space:pre-wrap"><%= channel.description_html %></span></p></div>
|
||||
</div>
|
||||
|
||||
<div class="pure-g h-box">
|
||||
|
@ -1,157 +1,146 @@
|
||||
<% item_watched = !item.is_a?(SearchChannel | SearchPlaylist | InvidiousPlaylist | Category) && env.get?("user").try &.as(User).watched.index(item.id) != nil %>
|
||||
<%-
|
||||
thin_mode = env.get("preferences").as(Preferences).thin_mode
|
||||
item_watched = !item.is_a?(SearchChannel | SearchPlaylist | InvidiousPlaylist | Category) && env.get?("user").try &.as(User).watched.index(item.id) != nil
|
||||
author_verified = item.responds_to?(:author_verified) && item.author_verified
|
||||
-%>
|
||||
|
||||
<div class="pure-u-1 pure-u-md-1-4">
|
||||
<div class="h-box">
|
||||
<% case item when %>
|
||||
<% when SearchChannel %>
|
||||
<a href="/channel/<%= item.ucid %>">
|
||||
<% if !env.get("preferences").as(Preferences).thin_mode %>
|
||||
<% if !thin_mode %>
|
||||
<a tabindex="-1" href="/channel/<%= item.ucid %>">
|
||||
<center>
|
||||
<img loading="lazy" tabindex="-1" style="width:56.25%" src="/ggpht<%= URI.parse(item.author_thumbnail).request_target.gsub(/=s\d+/, "=s176") %>" alt="" />
|
||||
<img loading="lazy" style="width:56.25%" src="/ggpht<%= URI.parse(item.author_thumbnail).request_target.gsub(/=s\d+/, "=s176") %>" alt="" />
|
||||
</center>
|
||||
<% end %>
|
||||
<p dir="auto"><%= HTML.escape(item.author) %><% if !item.author_verified.nil? && item.author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %></p>
|
||||
</a>
|
||||
</a>
|
||||
<%- else -%>
|
||||
<div class="thumbnail-placeholder" style="width:56.25%"></div>
|
||||
<% end %>
|
||||
|
||||
<div class="video-card-row flexible">
|
||||
<div class="flex-left"><a href="/channel/<%= item.ucid %>">
|
||||
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
|
||||
<%- if author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end -%>
|
||||
</p>
|
||||
</a></div>
|
||||
</div>
|
||||
|
||||
<p><%= translate_count(locale, "generic_subscribers_count", item.subscriber_count, NumberFormatting::Separator) %></p>
|
||||
<% if !item.auto_generated %><p><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p><% end %>
|
||||
<h5><%= item.description_html %></h5>
|
||||
<% when SearchPlaylist, InvidiousPlaylist %>
|
||||
<% if item.id.starts_with? "RD" %>
|
||||
<% url = "/mix?list=#{item.id}&continuation=#{URI.parse(item.thumbnail || "/vi/-----------").request_target.split("/")[2]}" %>
|
||||
<% else %>
|
||||
<% url = "/playlist?list=#{item.id}" %>
|
||||
<% end %>
|
||||
<%-
|
||||
if item.id.starts_with? "RD"
|
||||
link_url = "/mix?list=#{item.id}&continuation=#{URI.parse(item.thumbnail || "/vi/-----------").request_target.split("/")[2]}"
|
||||
else
|
||||
link_url = "/playlist?list=#{item.id}"
|
||||
end
|
||||
-%>
|
||||
|
||||
<a style="width:100%" href="<%= url %>">
|
||||
<% if !env.get("preferences").as(Preferences).thin_mode %>
|
||||
<div class="thumbnail">
|
||||
<img loading="lazy" tabindex="-1" class="thumbnail" src="<%= URI.parse(item.thumbnail || "/").request_target %>" alt="" />
|
||||
<p class="length"><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
<p dir="auto"><%= HTML.escape(item.title) %></p>
|
||||
</a>
|
||||
<a href="/channel/<%= item.ucid %>">
|
||||
<p dir="auto"><b><%= HTML.escape(item.author) %><% if !item.is_a?(InvidiousPlaylist) && !item.author_verified.nil? && item.author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %></b></p>
|
||||
</a>
|
||||
<% when MixVideo %>
|
||||
<a href="/watch?v=<%= item.id %>&list=<%= item.rdid %>">
|
||||
<% if !env.get("preferences").as(Preferences).thin_mode %>
|
||||
<div class="thumbnail">
|
||||
<img loading="lazy" tabindex="-1" class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg" alt="" />
|
||||
<% if item.length_seconds != 0 %>
|
||||
<p class="length"><%= recode_length_seconds(item.length_seconds) %></p>
|
||||
<% end %>
|
||||
<div class="thumbnail">
|
||||
<%- if !thin_mode %>
|
||||
<a tabindex="-1" href="<%= link_url %>">
|
||||
<img loading="lazy" class="thumbnail" src="<%= URI.parse(item.thumbnail || "/").request_target %>" alt="" />
|
||||
</a>
|
||||
<%- else -%>
|
||||
<div class="thumbnail-placeholder"></div>
|
||||
<%- end -%>
|
||||
|
||||
<% if item_watched %>
|
||||
<div class="watched-overlay"></div>
|
||||
<div class="watched-indicator" data-length="<%= item.length_seconds %>" data-id="<%= item.id %>"></div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<p dir="auto"><%= HTML.escape(item.title) %></p>
|
||||
</a>
|
||||
<a href="/channel/<%= item.ucid %>">
|
||||
<p dir="auto"><b><%= HTML.escape(item.author) %></b></p>
|
||||
</a>
|
||||
<% when PlaylistVideo %>
|
||||
<a style="width:100%" href="/watch?v=<%= item.id %>&list=<%= item.plid %>&index=<%= item.index %>">
|
||||
<% if !env.get("preferences").as(Preferences).thin_mode %>
|
||||
<div class="thumbnail">
|
||||
<img loading="lazy" tabindex="-1" class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg" alt="" />
|
||||
<div class="bottom-right-overlay">
|
||||
<p class="length"><%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %></p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if plid_form = env.get?("remove_playlist_items") %>
|
||||
<form data-onsubmit="return_false" action="/playlist_ajax?action_remove_video=1&set_video_id=<%= item.index %>&playlist_id=<%= plid_form %>&referer=<%= env.get("current_page") %>" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||
<p class="watched">
|
||||
<button type="submit" style="all:unset" data-onclick="remove_playlist_item" data-index="<%= item.index %>" data-plid="<%= plid_form %>"><i class="icon ion-md-trash"></i></button>
|
||||
</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 %>
|
||||
|
||||
<% if item_watched %>
|
||||
<div class="watched-overlay"></div>
|
||||
<div class="watched-indicator" data-length="<%= item.length_seconds %>" data-id="<%= item.id %>"></div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<p dir="auto"><%= HTML.escape(item.title) %></p>
|
||||
</a>
|
||||
|
||||
<div class="video-card-row flexible">
|
||||
<div class="flex-left"><a href="/channel/<%= item.ucid %>">
|
||||
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %></p>
|
||||
</a></div>
|
||||
<% endpoint_params = "?v=#{item.id}&list=#{item.plid}" %>
|
||||
<%= rendered "components/video-context-buttons" %>
|
||||
<div class="video-card-row">
|
||||
<a href="<%= link_url %>"><p dir="auto"><%= HTML.escape(item.title) %></p></a>
|
||||
</div>
|
||||
|
||||
<div class="video-card-row flexible">
|
||||
<div class="flex-left">
|
||||
<% if item.responds_to?(:premiere_timestamp) && item.premiere_timestamp.try &.> Time.utc %>
|
||||
<p dir="auto"><%= translate(locale, "Premieres in `x`", recode_date((item.premiere_timestamp.as(Time) - Time.utc).ago, locale)) %></p>
|
||||
<% elsif Time.utc - item.published > 1.minute %>
|
||||
<p dir="auto"><%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if item.responds_to?(:views) && item.views %>
|
||||
<div class="flex-right">
|
||||
<p dir="auto"><%= translate_count(locale, "generic_views_count", item.views || 0, NumberFormatting::Short) %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="flex-left"><a href="/channel/<%= item.ucid %>">
|
||||
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
|
||||
<%- if author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end -%>
|
||||
</p>
|
||||
</a></div>
|
||||
</div>
|
||||
<% when Category %>
|
||||
<% else %>
|
||||
<a style="width:100%" href="/watch?v=<%= item.id %>">
|
||||
<% if !env.get("preferences").as(Preferences).thin_mode %>
|
||||
<div class="thumbnail">
|
||||
<img loading="lazy" tabindex="-1" class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg" alt="" />
|
||||
<% if env.get? "show_watched" %>
|
||||
<form data-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="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||
<p class="watched">
|
||||
<button type="submit" style="all:unset" data-onclick="mark_watched" data-id="<%= item.id %>">
|
||||
<i data-mouse="switch_classes" data-switch-classes="ion-ios-eye-off,ion-ios-eye" class="icon ion-ios-eye"></i>
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
<% elsif plid_form = env.get? "add_playlist_items" %>
|
||||
<form data-onsubmit="return_false" action="/playlist_ajax?action_add_video=1&video_id=<%= item.id %>&playlist_id=<%= plid_form %>&referer=<%= env.get("current_page") %>" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||
<p class="watched">
|
||||
<button type="submit" style="all:unset" data-onclick="add_playlist_item" data-id="<%= item.id %>" data-plid="<%= plid_form %>"><i class="icon ion-md-add"></i></button>
|
||||
</p>
|
||||
</form>
|
||||
<% end %>
|
||||
<%-
|
||||
# `endpoint_params` is used for the "video-context-buttons" component
|
||||
if item.is_a?(PlaylistVideo)
|
||||
link_url = "/watch?v=#{item.id}&list=#{item.plid}&index=#{item.index}"
|
||||
endpoint_params = "?v=#{item.id}&list=#{item.plid}"
|
||||
elsif item.is_a?(MixVideo)
|
||||
link_url = "/watch?v=#{item.id}&list=#{item.rdid}"
|
||||
endpoint_params = "?v=#{item.id}&list=#{item.rdid}"
|
||||
else
|
||||
link_url = "/watch?v=#{item.id}"
|
||||
endpoint_params = "?v=#{item.id}"
|
||||
end
|
||||
-%>
|
||||
|
||||
<% if item.responds_to?(:live_now) && item.live_now %>
|
||||
<p class="length" dir="auto"><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 class="thumbnail">
|
||||
<%- if !thin_mode -%>
|
||||
<a tabindex="-1" href="<%= link_url %>">
|
||||
<img loading="lazy" class="thumbnail" src="/vi/<%= item.id %>/mqdefault.jpg" alt="" />
|
||||
|
||||
<% if item_watched %>
|
||||
<div class="watched-overlay"></div>
|
||||
<div class="watched-indicator" data-length="<%= item.length_seconds %>" data-id="<%= item.id %>"></div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<p dir="auto"><%= HTML.escape(item.title) %></p>
|
||||
</a>
|
||||
</a>
|
||||
<%- else -%>
|
||||
<div class="thumbnail-placeholder"></div>
|
||||
<%- end -%>
|
||||
|
||||
<div class="top-left-overlay">
|
||||
<%- if env.get? "show_watched" -%>
|
||||
<form data-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="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||
<button type="submit" class="pure-button pure-button-secondary low-profile"
|
||||
data-onclick="mark_watched" data-id="<%= item.id %>">
|
||||
<i data-mouse="switch_classes" data-switch-classes="ion-ios-eye-off,ion-ios-eye" class="icon ion-ios-eye"></i>
|
||||
</button>
|
||||
</form>
|
||||
<%- end -%>
|
||||
|
||||
<%- if plid_form = env.get?("add_playlist_items") -%>
|
||||
<%- form_parameters = "action_add_video=1&video_id=#{item.id}&playlist_id=#{plid_form}&referer=#{env.get("current_page")}" -%>
|
||||
<form data-onsubmit="return_false" action="/playlist_ajax?<%= form_parameters %>" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||
<button type="submit" class="pure-button pure-button-secondary low-profile"
|
||||
data-onclick="add_playlist_item" data-id="<%= item.id %>" data-plid="<%= plid_form %>"><i class="icon ion-md-add"></i></button>
|
||||
</form>
|
||||
<%- elsif item.is_a?(PlaylistVideo) && (plid_form = env.get?("remove_playlist_items")) -%>
|
||||
<%- form_parameters = "action_remove_video=1&set_video_id=#{item.index}&playlist_id=#{plid_form}&referer=#{env.get("current_page")}" -%>
|
||||
<form data-onsubmit="return_false" action="/playlist_ajax?<%= form_parameters %>" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||
<button type="submit" class="pure-button pure-button-secondary low-profile"
|
||||
data-onclick="remove_playlist_item" data-index="<%= item.index %>" data-plid="<%= plid_form %>"><i class="icon ion-md-trash"></i></button>
|
||||
</form>
|
||||
<%- end -%>
|
||||
</div>
|
||||
|
||||
<div class="bottom-right-overlay">
|
||||
<%- if item.responds_to?(:live_now) && item.live_now -%>
|
||||
<p class="length" dir="auto"><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>
|
||||
</div>
|
||||
|
||||
<div class="video-card-row">
|
||||
<a href="<%= link_url %>"><p dir="auto"><%= HTML.escape(item.title) %></p></a>
|
||||
</div>
|
||||
|
||||
<div class="video-card-row flexible">
|
||||
<div class="flex-left"><a href="/channel/<%= item.ucid %>">
|
||||
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %><% if !item.is_a?(ChannelVideo) && !item.author_verified.nil? && item.author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %></p>
|
||||
<p class="channel-name" dir="auto"><%= HTML.escape(item.author) %>
|
||||
<%- if author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end -%>
|
||||
</p>
|
||||
</a></div>
|
||||
|
||||
<% endpoint_params = "?v=#{item.id}" %>
|
||||
<%= rendered "components/video-context-buttons" %>
|
||||
</div>
|
||||
|
||||
@ -159,7 +148,7 @@
|
||||
<div class="flex-left">
|
||||
<% if item.responds_to?(:premiere_timestamp) && item.premiere_timestamp.try &.> Time.utc %>
|
||||
<p class="video-data" dir="auto"><%= translate(locale, "Premieres in `x`", recode_date((item.premiere_timestamp.as(Time) - Time.utc).ago, locale)) %></p>
|
||||
<% elsif Time.utc - item.published > 1.minute %>
|
||||
<% elsif item.responds_to?(:published) && (Time.utc - item.published) > 1.minute %>
|
||||
<p class="video-data" dir="auto"><%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %></p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
11
src/invidious/views/components/items_paginated.ecr
Normal file
11
src/invidious/views/components/items_paginated.ecr
Normal file
@ -0,0 +1,11 @@
|
||||
<%= page_nav_html %>
|
||||
|
||||
<div class="pure-g">
|
||||
<%- items.each do |item| -%>
|
||||
<%= rendered "components/item" %>
|
||||
<%- end -%>
|
||||
</div>
|
||||
|
||||
<%= page_nav_html %>
|
||||
|
||||
<script src="/js/watched_indicator.js"></script>
|
@ -1,22 +1,18 @@
|
||||
<% if user %>
|
||||
<% if subscriptions.includes? ucid %>
|
||||
<p>
|
||||
<form action="/subscription_ajax?action_remove_subscriptions=1&c=<%= ucid %>&referer=<%= env.get("current_page") %>" method="post">
|
||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||
<button data-type="unsubscribe" id="subscribe" class="pure-button pure-button-primary">
|
||||
<b><input style="all:unset" type="submit" value="<%= translate(locale, "Unsubscribe") %> | <%= sub_count_text %>"></b>
|
||||
</button>
|
||||
</form>
|
||||
</p>
|
||||
<% else %>
|
||||
<p>
|
||||
<form 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="<%= HTML.escape(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||
<button data-type="subscribe" id="subscribe" class="pure-button pure-button-primary">
|
||||
<b><input style="all:unset" type="submit" value="<%= translate(locale, "Subscribe") %> | <%= sub_count_text %>"></b>
|
||||
</button>
|
||||
</form>
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<script id="subscribe_data" type="application/json">
|
||||
@ -33,10 +29,8 @@
|
||||
</script>
|
||||
<script src="/js/subscribe_widget.js?v=<%= ASSET_COMMIT %>"></script>
|
||||
<% else %>
|
||||
<p>
|
||||
<a id="subscribe" class="pure-button pure-button-primary"
|
||||
href="/login?referer=<%= env.get("current_page") %>">
|
||||
<b><%= translate(locale, "Subscribe") %> | <%= sub_count_text %></b>
|
||||
</a>
|
||||
</p>
|
||||
<% end %>
|
||||
|
@ -1,4 +1,4 @@
|
||||
<div class="flex-right">
|
||||
<div class="flex-right flexible">
|
||||
<div class="icon-buttons">
|
||||
<a title="<%=translate(locale, "videoinfo_watch_on_youTube")%>" href="https://www.youtube.com/watch<%=endpoint_params%>">
|
||||
<i class="icon ion-logo-youtube"></i>
|
||||
|
@ -6,35 +6,43 @@
|
||||
<% end %>
|
||||
|
||||
<form class="pure-form" action="/edit_playlist?list=<%= plid %>" method="post">
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-2-3">
|
||||
<div class="h-box flexible">
|
||||
<div class="flex-right button-container">
|
||||
<div class="pure-u">
|
||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/playlist?list=<%= plid %>">
|
||||
<i class="icon ion-md-close"></i> <%= translate(locale, "generic_button_cancel") %>
|
||||
</a>
|
||||
</div>
|
||||
<div class="pure-u">
|
||||
<button class="pure-button pure-button-secondary low-profile" dir="auto" type="submit">
|
||||
<i class="icon ion-md-save"></i> <%= translate(locale, "generic_button_save") %>
|
||||
</button>
|
||||
</div>
|
||||
<div class="pure-u">
|
||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/delete_playlist?list=<%= plid %>">
|
||||
<i class="icon ion-md-trash"></i> <%= translate(locale, "generic_button_delete") %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-box flexible title">
|
||||
<div>
|
||||
<h3><input class="pure-input-1" maxlength="150" name="title" type="text" value="<%= title %>"></h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-box">
|
||||
<div class="pure-u-1-1">
|
||||
<b>
|
||||
<%= HTML.escape(playlist.author) %> |
|
||||
<%= translate_count(locale, "generic_videos_count", playlist.video_count) %> |
|
||||
<%= translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %> |
|
||||
<i class="icon <%= {"ion-md-globe", "ion-ios-unlock", "ion-ios-lock"}[playlist.privacy.value] %>"></i>
|
||||
<select name="privacy">
|
||||
<% {"Public", "Unlisted", "Private"}.each do |option| %>
|
||||
<option value="<%= option %>" <% if option == playlist.privacy.to_s %>selected<% end %>><%= translate(locale, option) %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
</b>
|
||||
</div>
|
||||
<div class="pure-u-1-3" style="text-align:right">
|
||||
<h3>
|
||||
<div class="pure-g user-field">
|
||||
<div class="pure-u-1-3">
|
||||
<a href="javascript:void(0)">
|
||||
<button type="submit" style="all:unset">
|
||||
<i class="icon ion-md-save"></i>
|
||||
</button>
|
||||
</a>
|
||||
</div>
|
||||
<div class="pure-u-1-3"><a href="/delete_playlist?list=<%= plid %>"><i class="icon ion-md-trash"></i></a></div>
|
||||
<div class="pure-u-1-3"><a href="/feed/playlist/<%= plid %>"><i class="icon ion-logo-rss"></i></a></div>
|
||||
</div>
|
||||
</h3>
|
||||
<select name="privacy">
|
||||
<%- {"Public", "Unlisted", "Private"}.each do |option| -%>
|
||||
<option value="<%= option %>" <% if option == playlist.privacy.to_s %>selected<% end %>><%= translate(locale, option) %></option>
|
||||
<%- end -%>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -44,40 +52,9 @@
|
||||
<input type="hidden" name="csrf_token" value="<%= HTML.escape(csrf_token) %>">
|
||||
</form>
|
||||
|
||||
<% if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email %>
|
||||
<div class="h-box" style="text-align:right">
|
||||
<h3>
|
||||
<a href="/add_playlist_items?list=<%= plid %>"><i class="icon ion-md-add"></i></a>
|
||||
</h3>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="h-box">
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<div class="pure-g">
|
||||
<% videos.each do |item| %>
|
||||
<%= rendered "components/item" %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<script src="/js/watched_indicator.js"></script>
|
||||
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-1 pure-u-lg-1-5">
|
||||
<% if page > 1 %>
|
||||
<a href="/playlist?list=<%= playlist.id %>&page=<%= page - 1 %>">
|
||||
<%= translate(locale, "Previous page") %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-lg-3-5"></div>
|
||||
<div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
|
||||
<% if videos.size == 100 %>
|
||||
<a href="/playlist?list=<%= playlist.id %>&page=<%= page + 1 %>">
|
||||
<%= translate(locale, "Next page") %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<%= rendered "components/items_paginated" %>
|
||||
|
@ -31,39 +31,29 @@
|
||||
<% watched.each do |item| %>
|
||||
<div class="pure-u-1 pure-u-md-1-4">
|
||||
<div class="h-box">
|
||||
<a style="width:100%" href="/watch?v=<%= item %>">
|
||||
<% if !env.get("preferences").as(Preferences).thin_mode %>
|
||||
<div class="thumbnail">
|
||||
<img class="thumbnail" src="/vi/<%= item %>/mqdefault.jpg" alt="" />
|
||||
<form data-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.encode_www_form(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||
<p class="watched">
|
||||
<button type="submit" style="all:unset" data-onclick="mark_unwatched" data-id="<%= item %>"><i class="icon ion-md-trash"></i></button>
|
||||
</p>
|
||||
</form>
|
||||
</div>
|
||||
<p></p>
|
||||
<% end %>
|
||||
</a>
|
||||
<div class="thumbnail">
|
||||
<a style="width:100%" href="/watch?v=<%= item %>">
|
||||
<img class="thumbnail" src="/vi/<%= item %>/mqdefault.jpg" alt="" />
|
||||
</a>
|
||||
|
||||
<div class="top-left-overlay"><div class="watched">
|
||||
<form data-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.encode_www_form(env.get?("csrf_token").try &.as(String) || "") %>">
|
||||
<button type="submit" class="pure-button pure-button-secondary low-profile"
|
||||
data-onclick="mark_unwatched" data-id="<%= item %>"><i class="icon ion-md-trash"></i></button>
|
||||
</form>
|
||||
</div></div>
|
||||
</div>
|
||||
<p></p>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-1 pure-u-lg-1-5">
|
||||
<% if page > 1 %>
|
||||
<a href="/feed/history?page=<%= page - 1 %><% if env.params.query["max_results"]? %>&max_results=<%= max_results %><% end %>">
|
||||
<%= translate(locale, "Previous page") %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-lg-3-5"></div>
|
||||
<div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
|
||||
<% if watched.size >= max_results %>
|
||||
<a href="/feed/history?page=<%= page + 1 %><% if env.params.query["max_results"]? %>&max_results=<%= max_results %><% end %>">
|
||||
<%= translate(locale, "Next page") %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<%=
|
||||
IV::Frontend::Pagination.nav_numeric(locale,
|
||||
base_url: base_url,
|
||||
current_page: page,
|
||||
show_next: (watched.size >= max_results)
|
||||
)
|
||||
%>
|
||||
|
@ -56,6 +56,7 @@
|
||||
</script>
|
||||
<script src="/js/watched_widget.js"></script>
|
||||
|
||||
|
||||
<div class="pure-g">
|
||||
<% videos.each do |item| %>
|
||||
<%= rendered "components/item" %>
|
||||
@ -64,20 +65,10 @@
|
||||
|
||||
<script src="/js/watched_indicator.js"></script>
|
||||
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-1 pure-u-lg-1-5">
|
||||
<% if page > 1 %>
|
||||
<a href="/feed/subscriptions?page=<%= page - 1 %><% if env.params.query["max_results"]? %>&max_results=<%= max_results %><% end %>">
|
||||
<%= translate(locale, "Previous page") %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-lg-3-5"></div>
|
||||
<div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
|
||||
<% if (videos.size + notifications.size) == max_results %>
|
||||
<a href="/feed/subscriptions?page=<%= page + 1 %><% if env.params.query["max_results"]? %>&max_results=<%= max_results %><% end %>">
|
||||
<%= translate(locale, "Next page") %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<%=
|
||||
IV::Frontend::Pagination.nav_numeric(locale,
|
||||
base_url: base_url,
|
||||
current_page: page,
|
||||
show_next: ((videos.size + notifications.size) == max_results)
|
||||
)
|
||||
%>
|
||||
|
@ -4,38 +4,5 @@
|
||||
|
||||
<hr/>
|
||||
|
||||
<div class="pure-g h-box v-box">
|
||||
<div class="pure-u-1 pure-u-lg-1-5">
|
||||
<%- if page > 1 -%>
|
||||
<a href="<%= url_prev_page %>"><%= translate(locale, "Previous page") %></a>
|
||||
<%- end -%>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-lg-3-5"></div>
|
||||
<div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
|
||||
<%- if videos.size >= 60 -%>
|
||||
<a href="<%= url_next_page %>"><%= translate(locale, "Next page") %></a>
|
||||
<%- end -%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="pure-g">
|
||||
<%- videos.each do |item| -%>
|
||||
<%= rendered "components/item" %>
|
||||
<%- end -%>
|
||||
</div>
|
||||
|
||||
<script src="/js/watched_indicator.js"></script>
|
||||
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-1 pure-u-lg-1-5">
|
||||
<%- if page > 1 -%>
|
||||
<a href="<%= url_prev_page %>"><%= translate(locale, "Previous page") %></a>
|
||||
<%- end -%>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-lg-3-5"></div>
|
||||
<div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
|
||||
<%- if videos.size >= 60 -%>
|
||||
<a href="<%= url_next_page %>"><%= translate(locale, "Next page") %></a>
|
||||
<%- end -%>
|
||||
</div>
|
||||
</div>
|
||||
<%= rendered "components/items_paginated" %>
|
||||
|
@ -6,9 +6,50 @@
|
||||
<link rel="alternate" type="application/rss+xml" title="RSS" href="/feed/playlist/<%= plid %>" />
|
||||
<% end %>
|
||||
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-2-3">
|
||||
<h3><%= title %></h3>
|
||||
<div class="h-box flexible title">
|
||||
<div class="flex-left"><h3><%= title %></h3></div>
|
||||
|
||||
<div class="flex-right button-container">
|
||||
<%- if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email -%>
|
||||
<div class="pure-u">
|
||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/add_playlist_items?list=<%= plid %>">
|
||||
<i class="icon ion-md-add"></i> <%= translate(locale, "playlist_button_add_items") %>
|
||||
</a>
|
||||
</div>
|
||||
<div class="pure-u">
|
||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/edit_playlist?list=<%= plid %>">
|
||||
<i class="icon ion-md-create"></i> <%= translate(locale, "generic_button_edit") %>
|
||||
</a>
|
||||
</div>
|
||||
<div class="pure-u">
|
||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/delete_playlist?list=<%= plid %>">
|
||||
<i class="icon ion-md-trash"></i> <%= translate(locale, "generic_button_delete") %>
|
||||
</a>
|
||||
</div>
|
||||
<%- else -%>
|
||||
<div class="pure-u">
|
||||
<%- if IV::Database::Playlists.exists?(playlist.id) -%>
|
||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/subscribe_playlist?list=<%= plid %>">
|
||||
<i class="icon ion-md-add"></i> <%= translate(locale, "Subscribe") %>
|
||||
</a>
|
||||
<%- else -%>
|
||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/delete_playlist?list=<%= plid %>">
|
||||
<i class="icon ion-md-trash"></i> <%= translate(locale, "Unsubscribe") %>
|
||||
</a>
|
||||
<%- end -%>
|
||||
</div>
|
||||
<%- end -%>
|
||||
|
||||
<div class="pure-u">
|
||||
<a class="pure-button pure-button-secondary low-profile" dir="auto" href="/feed/playlist/<%= plid %>">
|
||||
<i class="icon ion-logo-rss"></i> <%= translate(locale, "generic_button_rss") %>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-box">
|
||||
<div class="pure-u-1-1">
|
||||
<% if playlist.is_a? InvidiousPlaylist %>
|
||||
<b>
|
||||
<% if playlist.author == user.try &.email %>
|
||||
@ -54,37 +95,12 @@
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="pure-u-1-3" style="text-align:right">
|
||||
<h3>
|
||||
<div class="pure-g user-field">
|
||||
<% if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email %>
|
||||
<div class="pure-u-1-3"><a href="/edit_playlist?list=<%= plid %>"><i class="icon ion-md-create"></i></a></div>
|
||||
<div class="pure-u-1-3"><a href="/delete_playlist?list=<%= plid %>"><i class="icon ion-md-trash"></i></a></div>
|
||||
<% else %>
|
||||
<% if Invidious::Database::Playlists.exists?(playlist.id) %>
|
||||
<div class="pure-u-1-3"><a href="/subscribe_playlist?list=<%= plid %>"><i class="icon ion-md-add"></i></a></div>
|
||||
<% else %>
|
||||
<div class="pure-u-1-3"><a href="/delete_playlist?list=<%= plid %>"><i class="icon ion-md-trash"></i></a></div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<div class="pure-u-1-3"><a href="/feed/playlist/<%= plid %>"><i class="icon ion-logo-rss"></i></a></div>
|
||||
</div>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-box">
|
||||
<div id="descriptionWrapper"><%= playlist.description_html %></div>
|
||||
</div>
|
||||
|
||||
<% if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email %>
|
||||
<div class="h-box" style="text-align:right">
|
||||
<h3>
|
||||
<a href="/add_playlist_items?list=<%= plid %>"><i class="icon ion-md-add"></i></a>
|
||||
</h3>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="h-box">
|
||||
<hr>
|
||||
</div>
|
||||
@ -100,28 +116,5 @@
|
||||
<script src="/js/playlist_widget.js?v=<%= ASSET_COMMIT %>"></script>
|
||||
<% end %>
|
||||
|
||||
<div class="pure-g">
|
||||
<% videos.each do |item| %>
|
||||
<%= rendered "components/item" %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<script src="/js/watched_indicator.js"></script>
|
||||
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-1 pure-u-lg-1-5">
|
||||
<% if page > 1 %>
|
||||
<a href="/playlist?list=<%= playlist.id %>&page=<%= page - 1 %>">
|
||||
<%= translate(locale, "Previous page") %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-lg-3-5"></div>
|
||||
<div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
|
||||
<% if page_count != 1 && page < page_count %>
|
||||
<a href="/playlist?list=<%= playlist.id %>&page=<%= page + 1 %>">
|
||||
<%= translate(locale, "Next page") %>
|
||||
</a>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<%= rendered "components/items_paginated" %>
|
||||
|
@ -7,21 +7,8 @@
|
||||
<%= Invidious::Frontend::SearchFilters.generate(query.filters, query.text, query.page, locale) %>
|
||||
<hr/>
|
||||
|
||||
<div class="pure-g h-box v-box">
|
||||
<div class="pure-u-1 pure-u-lg-1-5">
|
||||
<%- if query.page > 1 -%>
|
||||
<a href="<%= url_prev_page %>"><%= translate(locale, "Previous page") %></a>
|
||||
<%- end -%>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-lg-3-5"></div>
|
||||
<div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
|
||||
<%- if videos.size >= 20 -%>
|
||||
<a href="<%= url_next_page %>"><%= translate(locale, "Next page") %></a>
|
||||
<%- end -%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<%- if videos.empty? -%>
|
||||
<%- if items.empty? -%>
|
||||
<div class="h-box no-results-error">
|
||||
<div>
|
||||
<%= translate(locale, "search_message_no_results") %><br/><br/>
|
||||
@ -30,25 +17,5 @@
|
||||
</div>
|
||||
</div>
|
||||
<%- else -%>
|
||||
<div class="pure-g">
|
||||
<%- videos.each do |item| -%>
|
||||
<%= rendered "components/item" %>
|
||||
<%- end -%>
|
||||
</div>
|
||||
<%= rendered "components/items_paginated" %>
|
||||
<%- end -%>
|
||||
|
||||
<script src="/js/watched_indicator.js"></script>
|
||||
|
||||
<div class="pure-g h-box">
|
||||
<div class="pure-u-1 pure-u-lg-1-5">
|
||||
<%- if query.page > 1 -%>
|
||||
<a href="<%= url_prev_page %>"><%= translate(locale, "Previous page") %></a>
|
||||
<%- end -%>
|
||||
</div>
|
||||
<div class="pure-u-1 pure-u-lg-3-5"></div>
|
||||
<div class="pure-u-1 pure-u-lg-1-5" style="text-align:right">
|
||||
<%- if videos.size >= 20 -%>
|
||||
<a href="<%= url_next_page %>"><%= translate(locale, "Next page") %></a>
|
||||
<%- end -%>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -204,19 +204,28 @@ we're going to need to do it here in order to allow for translations.
|
||||
</div>
|
||||
|
||||
<div class="pure-u-1 <% if params.related_videos || plid %>pure-u-lg-3-5<% else %>pure-u-md-4-5<% end %>">
|
||||
<div class="h-box">
|
||||
<a href="/channel/<%= video.ucid %>" style="display:block;width:fit-content;width:-moz-fit-content">
|
||||
<div class="channel-profile">
|
||||
<% if !video.author_thumbnail.empty? %>
|
||||
<img src="/ggpht<%= URI.parse(video.author_thumbnail).request_target %>" alt="" />
|
||||
<% end %>
|
||||
<span id="channel-name"><%= author %><% if !video.author_verified.nil? && video.author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %></span>
|
||||
|
||||
<div class="pure-g h-box flexible title">
|
||||
<div class="pure-u-1-2 flex-left flexible">
|
||||
<a href="/channel/<%= video.ucid %>">
|
||||
<div class="channel-profile">
|
||||
<% if !video.author_thumbnail.empty? %>
|
||||
<img src="/ggpht<%= URI.parse(video.author_thumbnail).request_target %>" alt="" />
|
||||
<% end %>
|
||||
<span id="channel-name"><%= author %><% if !video.author_verified.nil? && video.author_verified %> <i class="icon ion ion-md-checkmark-circle"></i><% end %></span>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="pure-u-1-2 flex-right flexible button-container">
|
||||
<div class="pure-u">
|
||||
<% sub_count_text = video.sub_count_text %>
|
||||
<%= rendered "components/subscribe_widget" %>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<% sub_count_text = video.sub_count_text %>
|
||||
<%= rendered "components/subscribe_widget" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="h-box">
|
||||
<p id="published-date">
|
||||
<% if video.premiere_timestamp.try &.> Time.utc %>
|
||||
<b><%= video.premiere_timestamp.try { |t| translate(locale, "Premieres `x`", t.to_s("%B %-d, %R UTC")) } %></b>
|
||||
@ -295,15 +304,28 @@ we're going to need to do it here in order to allow for translations.
|
||||
|
||||
<% video.related_videos.each do |rv| %>
|
||||
<% if rv["id"]? %>
|
||||
<a href="/watch?v=<%= rv["id"] %>&listen=<%= params.listen %>">
|
||||
<% if !env.get("preferences").as(Preferences).thin_mode %>
|
||||
<div class="thumbnail">
|
||||
<div class="pure-u-1">
|
||||
|
||||
<div class="thumbnail">
|
||||
<%- if !env.get("preferences").as(Preferences).thin_mode -%>
|
||||
<a tabindex="-1" href="/watch?v=<%= rv["id"] %>&listen=<%= params.listen %>">
|
||||
<img loading="lazy" class="thumbnail" src="/vi/<%= rv["id"] %>/mqdefault.jpg" alt="" />
|
||||
<p class="length"><%= recode_length_seconds(rv["length_seconds"]?.try &.to_i? || 0) %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
<p style="width:100%"><%= rv["title"] %></p>
|
||||
</a>
|
||||
</a>
|
||||
<%- else -%>
|
||||
<div class="thumbnail-placeholder"></div>
|
||||
<%- end -%>
|
||||
|
||||
<div class="bottom-right-overlay">
|
||||
<%- if (length_seconds = rv["length_seconds"]?.try &.to_i?) && length_seconds != 0 -%>
|
||||
<p class="length"><%= recode_length_seconds(length_seconds) %></p>
|
||||
<%- end -%>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="video-card-row">
|
||||
<a href="/watch?v=<%= rv["id"] %>&listen=<%= params.listen %>"><p dir="auto"><%= HTML.escape(rv["title"]) %></p></a>
|
||||
</div>
|
||||
|
||||
<h5 class="pure-g">
|
||||
<div class="pure-u-14-24">
|
||||
<% if rv["ucid"]? %>
|
||||
@ -321,6 +343,8 @@ we're going to need to do it here in order to allow for translations.
|
||||
%></b>
|
||||
</div>
|
||||
</h5>
|
||||
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user