diff --git a/assets/css/default.css b/assets/css/default.css index 431a0427b..c31b24e5d 100644 --- a/assets/css/default.css +++ b/assets/css/default.css @@ -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 { diff --git a/locales/en-US.json b/locales/en-US.json index 29dd7a40c..74f43d905 100644 --- a/locales/en-US.json +++ b/locales/en-US.json @@ -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", diff --git a/locales/fr.json b/locales/fr.json index d2607a49a..2eb4dd2b0 100644 --- a/locales/fr.json +++ b/locales/fr.json @@ -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", diff --git a/src/invidious/frontend/pagination.cr b/src/invidious/frontend/pagination.cr new file mode 100644 index 000000000..3f931f4e7 --- /dev/null +++ b/src/invidious/frontend/pagination.cr @@ -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 << %() + + if locale_is_rtl?(locale) + # Inverted arrow ("previous" points to the right) + str << translate(locale, "Previous page") + str << "  " + str << %() + else + # Regular arrow ("previous" points to the left) + str << %() + str << "  " + str << translate(locale, "Previous page") + end + + str << "" + end + + private def next_page(str : String::Builder, locale : String?, url : String) + # Link + str << %() + + if locale_is_rtl?(locale) + # Inverted arrow ("next" points to the left) + str << %() + str << "  " + str << translate(locale, "Next page") + else + # Regular arrow ("next" points to the right) + str << translate(locale, "Next page") + str << "  " + str << %() + end + + str << "" + end + + def nav_numeric(locale : String?, *, base_url : String | URI, current_page : Int, show_next : Bool = true) + return String.build do |str| + str << %(
\n) + str << %(\n) + str << %(
\n\n) + end + end + + def nav_ctoken(locale : String?, *, base_url : String | URI, ctoken : String?) + return String.build do |str| + str << %(
\n) + str << %(\n) + str << %(
\n\n) + end + end +end diff --git a/src/invidious/helpers/i18n.cr b/src/invidious/helpers/i18n.cr index a9ed1f642..76e477a48 100644 --- a/src/invidious/helpers/i18n.cr +++ b/src/invidious/helpers/i18n.cr @@ -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 diff --git a/src/invidious/http_server/utils.cr b/src/invidious/http_server/utils.cr index e3f1fa0f2..222dfc4a7 100644 --- a/src/invidious/http_server/utils.cr +++ b/src/invidious/http_server/utils.cr @@ -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 diff --git a/src/invidious/routes/feeds.cr b/src/invidious/routes/feeds.cr index fc62c5a36..a8246b2ea 100644 --- a/src/invidious/routes/feeds.cr +++ b/src/invidious/routes/feeds.cr @@ -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 diff --git a/src/invidious/routes/playlists.cr b/src/invidious/routes/playlists.cr index 1dd3f32ed..5cb96809c 100644 --- a/src/invidious/routes/playlists.cr +++ b/src/invidious/routes/playlists.cr @@ -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.
#{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 diff --git a/src/invidious/routes/search.cr b/src/invidious/routes/search.cr index 6c3088dee..5be335339 100644 --- a/src/invidious/routes/search.cr +++ b/src/invidious/routes/search.cr @@ -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 diff --git a/src/invidious/views/add_playlist_items.ecr b/src/invidious/views/add_playlist_items.ecr index bcba74cfa..6aea82aec 100644 --- a/src/invidious/views/add_playlist_items.ecr +++ b/src/invidious/views/add_playlist_items.ecr @@ -31,33 +31,5 @@ -
- <% videos.each_slice(4) do |slice| %> - <% slice.each do |item| %> - <%= rendered "components/item" %> - <% end %> - <% end %> -
- - -<% if query %> - <%- query_encoded = URI.encode_www_form(query.text, space_to_plus: true) -%> -
-
- <% if query.page > 1 %> - - <%= translate(locale, "Previous page") %> - - <% end %> -
-
-
- <% if videos.size >= 20 %> - - <%= translate(locale, "Next page") %> - - <% end %> -
-
-<% end %> +<%= rendered "components/items_paginated" %> diff --git a/src/invidious/views/channel.ecr b/src/invidious/views/channel.ecr index 4b50e7a04..09df106d6 100644 --- a/src/invidious/views/channel.ecr +++ b/src/invidious/views/channel.ecr @@ -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 @@
-
-<% items.each do |item| %> - <%= rendered "components/item" %> -<% end %> -
- - -
-
-
- <% if next_continuation %> - - <%= translate(locale, "Next page") %> - - <% end %> -
-
+<%= rendered "components/items_paginated" %> diff --git a/src/invidious/views/components/channel_info.ecr b/src/invidious/views/components/channel_info.ecr index 598887604..f4164f31b 100644 --- a/src/invidious/views/components/channel_info.ecr +++ b/src/invidious/views/components/channel_info.ecr @@ -8,29 +8,30 @@ <% end %> -
-
+
+
<%= author %><% if !channel.verified.nil? && channel.verified %> <% end %>
-
-

- -

+ +
+
+ <% sub_count_text = number_to_short_text(channel.sub_count) %> + <%= rendered "components/subscribe_widget" %> +
+ +
-
-

<%= channel.description_html %>

-
-
- -
- <% sub_count_text = number_to_short_text(channel.sub_count) %> - <%= rendered "components/subscribe_widget" %> +

<%= channel.description_html %>

diff --git a/src/invidious/views/components/item.ecr b/src/invidious/views/components/item.ecr index 7cfd38db5..7ffd2d937 100644 --- a/src/invidious/views/components/item.ecr +++ b/src/invidious/views/components/item.ecr @@ -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 +-%>
<% case item when %> <% when SearchChannel %> - - <% if !env.get("preferences").as(Preferences).thin_mode %> + <% if !thin_mode %> +
- " alt="" /> + " alt="" />
- <% end %> -

<%= HTML.escape(item.author) %><% if !item.author_verified.nil? && item.author_verified %> <% end %>

-
+ + <%- else -%> +
+ <% end %> + + +

<%= translate_count(locale, "generic_subscribers_count", item.subscriber_count, NumberFormatting::Separator) %>

<% if !item.auto_generated %>

<%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %>

<% end %>
<%= item.description_html %>
<% 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 + -%> - - <% if !env.get("preferences").as(Preferences).thin_mode %> -
- " alt="" /> -

<%= translate_count(locale, "generic_videos_count", item.video_count, NumberFormatting::Separator) %>

-
- <% end %> -

<%= HTML.escape(item.title) %>

-
- -

<%= HTML.escape(item.author) %><% if !item.is_a?(InvidiousPlaylist) && !item.author_verified.nil? && item.author_verified %> <% end %>

-
- <% when MixVideo %> - - <% if !env.get("preferences").as(Preferences).thin_mode %> - - <% end %> -

<%= HTML.escape(item.title) %>

-
- -
- - <% endpoint_params = "?v=#{item.id}&list=#{item.plid}" %> - <%= rendered "components/video-context-buttons" %> +
-
- <% if item.responds_to?(:premiere_timestamp) && item.premiere_timestamp.try &.> Time.utc %> -

<%= translate(locale, "Premieres in `x`", recode_date((item.premiere_timestamp.as(Time) - Time.utc).ago, locale)) %>

- <% elsif Time.utc - item.published > 1.minute %> -

<%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %>

- <% end %> -
- - <% if item.responds_to?(:views) && item.views %> -
-

<%= translate_count(locale, "generic_views_count", item.views || 0, NumberFormatting::Short) %>

-
- <% end %> +
<% when Category %> <% else %> - - <% if !env.get("preferences").as(Preferences).thin_mode %> -
- - <% if env.get? "show_watched" %> -
" method="post"> - "> -

- -

-
- <% elsif plid_form = env.get? "add_playlist_items" %> -
" method="post"> - "> -

- -

-
- <% 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 %> -

<%= translate(locale, "LIVE") %>

- <% elsif item.length_seconds != 0 %> -

<%= recode_length_seconds(item.length_seconds) %>

- <% end %> +
- <% end %> -

<%= HTML.escape(item.title) %>

-
+ + <%- else -%> +
+ <%- end -%> + +
+ <%- if env.get? "show_watched" -%> +
" method="post"> + "> + +
+ <%- 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")}" -%> +
+ "> + +
+ <%- 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")}" -%> +
+ "> + +
+ <%- end -%> +
+ +
+ <%- if item.responds_to?(:live_now) && item.live_now -%> +

 <%= translate(locale, "LIVE") %>

+ <%- elsif item.length_seconds != 0 -%> +

<%= recode_length_seconds(item.length_seconds) %>

+ <%- end -%> +
+
+ + @@ -159,7 +148,7 @@
<% if item.responds_to?(:premiere_timestamp) && item.premiere_timestamp.try &.> Time.utc %>

<%= translate(locale, "Premieres in `x`", recode_date((item.premiere_timestamp.as(Time) - Time.utc).ago, locale)) %>

- <% elsif Time.utc - item.published > 1.minute %> + <% elsif item.responds_to?(:published) && (Time.utc - item.published) > 1.minute %>

<%= translate(locale, "Shared `x` ago", recode_date(item.published, locale)) %>

<% end %>
diff --git a/src/invidious/views/components/items_paginated.ecr b/src/invidious/views/components/items_paginated.ecr new file mode 100644 index 000000000..4534a0a30 --- /dev/null +++ b/src/invidious/views/components/items_paginated.ecr @@ -0,0 +1,11 @@ +<%= page_nav_html %> + +
+ <%- items.each do |item| -%> + <%= rendered "components/item" %> + <%- end -%> +
+ +<%= page_nav_html %> + + diff --git a/src/invidious/views/components/subscribe_widget.ecr b/src/invidious/views/components/subscribe_widget.ecr index b9d5f783b..05e4e253e 100644 --- a/src/invidious/views/components/subscribe_widget.ecr +++ b/src/invidious/views/components/subscribe_widget.ecr @@ -1,22 +1,18 @@ <% if user %> <% if subscriptions.includes? ucid %> -

" method="post"> ">
-

<% else %> -

" method="post"> ">
-

<% end %> <% else %> -

"> <%= translate(locale, "Subscribe") %> | <%= sub_count_text %> -

<% end %> diff --git a/src/invidious/views/components/video-context-buttons.ecr b/src/invidious/views/components/video-context-buttons.ecr index ddb6c983a..385ed6b36 100644 --- a/src/invidious/views/components/video-context-buttons.ecr +++ b/src/invidious/views/components/video-context-buttons.ecr @@ -1,4 +1,4 @@ -
+
" href="https://www.youtube.com/watch<%=endpoint_params%>"> @@ -6,7 +6,7 @@ " href="/watch<%=endpoint_params%>&listen=1"> - + <% if env.get("preferences").as(Preferences).automatic_instance_redirect%> " href="/redirect?referer=%2Fwatch<%=URI.encode_www_form(endpoint_params)%>"> diff --git a/src/invidious/views/edit_playlist.ecr b/src/invidious/views/edit_playlist.ecr index 548104c8b..34157c675 100644 --- a/src/invidious/views/edit_playlist.ecr +++ b/src/invidious/views/edit_playlist.ecr @@ -6,35 +6,43 @@ <% end %>
-
-
+ + +
+

+
+
+ +
+
<%= HTML.escape(playlist.author) %> | <%= translate_count(locale, "generic_videos_count", playlist.video_count) %> | - <%= translate(locale, "Updated `x` ago", recode_date(playlist.updated, locale)) %> | - "> - -
-
-

-
- -
-
-
-

+
@@ -44,40 +52,9 @@ -<% if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email %> -
-

- -

-
-<% end %> -

-
-<% videos.each do |item| %> - <%= rendered "components/item" %> -<% end %> -
- - -
-
- <% if page > 1 %> - - <%= translate(locale, "Previous page") %> - - <% end %> -
-
-
- <% if videos.size == 100 %> - - <%= translate(locale, "Next page") %> - - <% end %> -
-
+<%= rendered "components/items_paginated" %> diff --git a/src/invidious/views/feeds/history.ecr b/src/invidious/views/feeds/history.ecr index 2234b297f..bda4e1f3a 100644 --- a/src/invidious/views/feeds/history.ecr +++ b/src/invidious/views/feeds/history.ecr @@ -31,39 +31,29 @@ <% watched.each do |item| %> <% end %>
- +<%= + IV::Frontend::Pagination.nav_numeric(locale, + base_url: base_url, + current_page: page, + show_next: (watched.size >= max_results) + ) +%> diff --git a/src/invidious/views/feeds/subscriptions.ecr b/src/invidious/views/feeds/subscriptions.ecr index 9c69c5b06..c36bd00fd 100644 --- a/src/invidious/views/feeds/subscriptions.ecr +++ b/src/invidious/views/feeds/subscriptions.ecr @@ -56,6 +56,7 @@ +
<% videos.each do |item| %> <%= rendered "components/item" %> @@ -64,20 +65,10 @@ -
- -
-
- <% if (videos.size + notifications.size) == max_results %> - &max_results=<%= max_results %><% end %>"> - <%= translate(locale, "Next page") %> - - <% end %> -
-
+<%= + IV::Frontend::Pagination.nav_numeric(locale, + base_url: base_url, + current_page: page, + show_next: ((videos.size + notifications.size) == max_results) + ) +%> diff --git a/src/invidious/views/hashtag.ecr b/src/invidious/views/hashtag.ecr index 3351c21c2..2000337e5 100644 --- a/src/invidious/views/hashtag.ecr +++ b/src/invidious/views/hashtag.ecr @@ -4,38 +4,5 @@
-
-
- <%- if page > 1 -%> - <%= translate(locale, "Previous page") %> - <%- end -%> -
-
-
- <%- if videos.size >= 60 -%> - <%= translate(locale, "Next page") %> - <%- end -%> -
-
-
- <%- videos.each do |item| -%> - <%= rendered "components/item" %> - <%- end -%> -
- - - -
-
- <%- if page > 1 -%> - <%= translate(locale, "Previous page") %> - <%- end -%> -
-
-
- <%- if videos.size >= 60 -%> - <%= translate(locale, "Next page") %> - <%- end -%> -
-
+<%= rendered "components/items_paginated" %> diff --git a/src/invidious/views/playlist.ecr b/src/invidious/views/playlist.ecr index a04acf4cc..ee9ba87b4 100644 --- a/src/invidious/views/playlist.ecr +++ b/src/invidious/views/playlist.ecr @@ -6,9 +6,50 @@ <% end %> -
-
-

<%= title %>

+
+

<%= title %>

+ +
+ <%- if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email -%> + + + + <%- else -%> +
+ <%- if IV::Database::Playlists.exists?(playlist.id) -%> + +  <%= translate(locale, "Subscribe") %> + + <%- else -%> + +  <%= translate(locale, "Unsubscribe") %> + + <%- end -%> +
+ <%- end -%> + + +
+
+ +
+
<% if playlist.is_a? InvidiousPlaylist %> <% if playlist.author == user.try &.email %> @@ -54,37 +95,12 @@
<% end %>
-
-

-
- <% if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email %> -
-
- <% else %> - <% if Invidious::Database::Playlists.exists?(playlist.id) %> -
- <% else %> -
- <% end %> - <% end %> -
-
-

-
<%= playlist.description_html %>
-<% if playlist.is_a?(InvidiousPlaylist) && playlist.author == user.try &.email %> -
-

- -

-
-<% end %> -

@@ -100,28 +116,5 @@ <% end %> -
-<% videos.each do |item| %> - <%= rendered "components/item" %> -<% end %> -
- - -
-
- <% if page > 1 %> - - <%= translate(locale, "Previous page") %> - - <% end %> -
-
-
- <% if page_count != 1 && page < page_count %> - - <%= translate(locale, "Next page") %> - - <% end %> -
-
+<%= rendered "components/items_paginated" %> diff --git a/src/invidious/views/search.ecr b/src/invidious/views/search.ecr index a7469e36c..b13002140 100644 --- a/src/invidious/views/search.ecr +++ b/src/invidious/views/search.ecr @@ -7,21 +7,8 @@ <%= Invidious::Frontend::SearchFilters.generate(query.filters, query.text, query.page, locale) %>
-
-
- <%- if query.page > 1 -%> - <%= translate(locale, "Previous page") %> - <%- end -%> -
-
-
- <%- if videos.size >= 20 -%> - <%= translate(locale, "Next page") %> - <%- end -%> -
-
-<%- if videos.empty? -%> +<%- if items.empty? -%>
<%= translate(locale, "search_message_no_results") %>

@@ -30,25 +17,5 @@
<%- else -%> -
- <%- videos.each do |item| -%> - <%= rendered "components/item" %> - <%- end -%> -
+ <%= rendered "components/items_paginated" %> <%- end -%> - - - -
-
- <%- if query.page > 1 -%> - <%= translate(locale, "Previous page") %> - <%- end -%> -
-
-
- <%- if videos.size >= 20 -%> - <%= translate(locale, "Next page") %> - <%- end -%> -
-
diff --git a/src/invidious/views/watch.ecr b/src/invidious/views/watch.ecr index 5b3190f3d..498d57a1b 100644 --- a/src/invidious/views/watch.ecr +++ b/src/invidious/views/watch.ecr @@ -204,19 +204,28 @@ we're going to need to do it here in order to allow for translations.
-
- -
- <% if !video.author_thumbnail.empty? %> - - <% end %> - <%= author %><% if !video.author_verified.nil? && video.author_verified %> <% end %> + +
+ + +
+
+ <% sub_count_text = video.sub_count_text %> + <%= rendered "components/subscribe_widget" %>
- - - <% sub_count_text = video.sub_count_text %> - <%= rendered "components/subscribe_widget" %> +
+
+

<% if video.premiere_timestamp.try &.> Time.utc %> <%= video.premiere_timestamp.try { |t| translate(locale, "Premieres `x`", t.to_s("%B %-d, %R UTC")) } %> @@ -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"]? %> - &listen=<%= params.listen %>"> - <% if !env.get("preferences").as(Preferences).thin_mode %> -

+
+ + - <% end %> -

<%= rv["title"] %>

-
+ + <%- else -%> +
+ <%- end -%> + +
+ <%- if (length_seconds = rv["length_seconds"]?.try &.to_i?) && length_seconds != 0 -%> +

<%= recode_length_seconds(length_seconds) %>

+ <%- end -%> +
+
+ + +
<% if rv["ucid"]? %> @@ -321,6 +343,8 @@ we're going to need to do it here in order to allow for translations. %>
+ +
<% end %> <% end %>