summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsyeopite <syeopite@syeopite.dev>2025-02-26 13:54:25 -0800
committersyeopite <syeopite@syeopite.dev>2025-02-26 13:54:25 -0800
commitf95f87e448aec544df9004066f9ea45dd637b5dc (patch)
treebd1ef8333527f22d6efc8d5d51afad38968739ec
parent164d764d553921e6ee98facda241f13c2103ec90 (diff)
parent3c6019edd0c0ba2190c6c3ca8958339e05a18864 (diff)
downloadinvidious-f95f87e448aec544df9004066f9ea45dd637b5dc.tar.gz
invidious-f95f87e448aec544df9004066f9ea45dd637b5dc.tar.bz2
invidious-f95f87e448aec544df9004066f9ea45dd637b5dc.zip
Frontend: Add a first page and previous page buttons for channel navigation (#4123)
-rw-r--r--assets/js/pagination.js93
-rw-r--r--locales/de.json1
-rw-r--r--locales/en-US.json1
-rw-r--r--locales/ru.json1
-rw-r--r--src/invidious/frontend/pagination.cr32
-rw-r--r--src/invidious/views/channel.ecr6
-rw-r--r--src/invidious/views/components/items_paginated.ecr10
7 files changed, 139 insertions, 5 deletions
diff --git a/assets/js/pagination.js b/assets/js/pagination.js
new file mode 100644
index 00000000..2e560a34
--- /dev/null
+++ b/assets/js/pagination.js
@@ -0,0 +1,93 @@
+'use strict';
+
+const CURRENT_CONTINUATION = (new URL(document.location)).searchParams.get("continuation");
+const CONT_CACHE_KEY = `continuation_cache_${encodeURIComponent(window.location.pathname)}`;
+
+function get_data(){
+ return JSON.parse(sessionStorage.getItem(CONT_CACHE_KEY)) || [];
+}
+
+function save_data(){
+ const prev_data = get_data();
+ prev_data.push(CURRENT_CONTINUATION);
+
+ sessionStorage.setItem(CONT_CACHE_KEY, JSON.stringify(prev_data));
+}
+
+function button_press(){
+ let prev_data = get_data();
+ if (!prev_data.length) return null;
+
+ // Sanity check. Nowhere should the current continuation token exist in the cache
+ // but it can happen when using the browser's back feature. As such we'd need to travel
+ // back to the point where the current continuation token first appears in order to
+ // account for the rewind.
+ const conflict_at = prev_data.indexOf(CURRENT_CONTINUATION);
+ if (conflict_at != -1) {
+ prev_data.length = conflict_at;
+ }
+
+ const prev_ctoken = prev_data.pop();
+
+ // On the first page, the stored continuation token is null.
+ if (prev_ctoken === null) {
+ sessionStorage.removeItem(CONT_CACHE_KEY);
+ let url = set_continuation();
+ window.location.href = url;
+
+ return;
+ }
+
+ sessionStorage.setItem(CONT_CACHE_KEY, JSON.stringify(prev_data));
+ let url = set_continuation(prev_ctoken);
+
+ window.location.href = url;
+};
+
+// Method to set the current page's continuation token
+// Removes the continuation parameter when a continuation token is not given
+function set_continuation(prev_ctoken = null){
+ let url = window.location.href.split('?')[0];
+ let params = window.location.href.split('?')[1];
+ let url_params = new URLSearchParams(params);
+
+ if (prev_ctoken) {
+ url_params.set("continuation", prev_ctoken);
+ } else {
+ url_params.delete('continuation');
+ };
+
+ if(Array.from(url_params).length > 0){
+ return `${url}?${url_params.toString()}`;
+ } else {
+ return url;
+ }
+}
+
+addEventListener('DOMContentLoaded', function(){
+ const pagination_data = JSON.parse(document.getElementById('pagination-data').textContent);
+ const next_page_containers = document.getElementsByClassName("page-next-container");
+
+ for (let container of next_page_containers){
+ const next_page_button = container.getElementsByClassName("pure-button")
+
+ // exists?
+ if (next_page_button.length > 0){
+ next_page_button[0].addEventListener("click", save_data);
+ }
+ }
+
+ // Only add previous page buttons when not on the first page
+ if (CURRENT_CONTINUATION) {
+ const prev_page_containers = document.getElementsByClassName("page-prev-container")
+
+ for (let container of prev_page_containers) {
+ if (pagination_data.is_rtl) {
+ container.innerHTML = `<button class="pure-button pure-button-secondary">${pagination_data.prev_page}&nbsp;&nbsp;<i class="icon ion-ios-arrow-forward"></i></button>`
+ } else {
+ container.innerHTML = `<button class="pure-button pure-button-secondary"><i class="icon ion-ios-arrow-back"></i>&nbsp;&nbsp;${pagination_data.prev_page}</button>`
+ }
+ container.getElementsByClassName("pure-button")[0].addEventListener("click", button_press);
+ }
+ }
+});
diff --git a/locales/de.json b/locales/de.json
index 151f2abe..a9a62619 100644
--- a/locales/de.json
+++ b/locales/de.json
@@ -11,6 +11,7 @@
"last": "neueste",
"Next page": "Nächste Seite",
"Previous page": "Vorherige Seite",
+ "First page": "Erste Seite",
"Clear watch history?": "Verlauf löschen?",
"New password": "Neues Passwort",
"New passwords must match": "Neue Passwörter müssen übereinstimmen",
diff --git a/locales/en-US.json b/locales/en-US.json
index c23f6bc3..381bcab5 100644
--- a/locales/en-US.json
+++ b/locales/en-US.json
@@ -33,6 +33,7 @@
"last": "last",
"Next page": "Next page",
"Previous page": "Previous page",
+ "First page": "First page",
"Clear watch history?": "Clear watch history?",
"New password": "New password",
"New passwords must match": "New passwords must match",
diff --git a/locales/ru.json b/locales/ru.json
index 80c98de8..31ef1a33 100644
--- a/locales/ru.json
+++ b/locales/ru.json
@@ -11,6 +11,7 @@
"last": "последние",
"Next page": "Следующая страница",
"Previous page": "Предыдущая страница",
+ "First page": "Первая страница",
"Clear watch history?": "Очистить историю просмотров?",
"New password": "Новый пароль",
"New passwords must match": "Новые пароли не совпадают",
diff --git a/src/invidious/frontend/pagination.cr b/src/invidious/frontend/pagination.cr
index 3f931f4e..a29f5936 100644
--- a/src/invidious/frontend/pagination.cr
+++ b/src/invidious/frontend/pagination.cr
@@ -3,6 +3,24 @@ require "uri"
module Invidious::Frontend::Pagination
extend self
+ private def first_page(str : String::Builder, locale : String?, url : String)
+ str << %(<a href=") << url << %(" class="pure-button pure-button-secondary">)
+
+ if locale_is_rtl?(locale)
+ # Inverted arrow ("first" points to the right)
+ str << translate(locale, "First page")
+ str << "&nbsp;&nbsp;"
+ str << %(<i class="icon ion-ios-arrow-forward"></i>)
+ else
+ # Regular arrow ("first" points to the left)
+ str << %(<i class="icon ion-ios-arrow-back"></i>)
+ str << "&nbsp;&nbsp;"
+ str << translate(locale, "First page")
+ end
+
+ str << "</a>"
+ end
+
private def previous_page(str : String::Builder, locale : String?, url : String)
# Link
str << %(<a href=") << url << %(" class="pure-button pure-button-secondary">)
@@ -72,18 +90,24 @@ module Invidious::Frontend::Pagination
end
end
- def nav_ctoken(locale : String?, *, base_url : String | URI, ctoken : String?)
+ def nav_ctoken(locale : String?, *, base_url : String | URI, ctoken : String?, first_page : Bool, params : URI::Params)
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-prev-container flex-left">)
+
+ if !first_page
+ self.first_page(str, locale, base_url.to_s)
+ end
+
+ str << %(</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)
+ params["continuation"] = ctoken
+ url_next = HttpServer::Utils.add_params_to_url(base_url, params)
self.next_page(str, locale, url_next.to_s)
end
diff --git a/src/invidious/views/channel.ecr b/src/invidious/views/channel.ecr
index a84e44bc..1fe8ab7e 100644
--- a/src/invidious/views/channel.ecr
+++ b/src/invidious/views/channel.ecr
@@ -20,7 +20,9 @@
page_nav_html = IV::Frontend::Pagination.nav_ctoken(locale,
base_url: relative_url,
- ctoken: next_continuation
+ ctoken: next_continuation,
+ first_page: continuation.nil?,
+ params: env.params.query,
)
%>
@@ -40,6 +42,8 @@
<link rel="alternate" type="application/rss+xml" title="RSS" href="/feed/channel/<%= ucid %>" />
<%- end -%>
+<script src="/js/pagination.js?v=<%= ASSET_COMMIT %>"></script>
+
<link rel="alternate" href="<%= youtube_url %>">
<title><%= author %> - Invidious</title>
<% end %>
diff --git a/src/invidious/views/components/items_paginated.ecr b/src/invidious/views/components/items_paginated.ecr
index 4534a0a3..f69df3fe 100644
--- a/src/invidious/views/components/items_paginated.ecr
+++ b/src/invidious/views/components/items_paginated.ecr
@@ -8,4 +8,14 @@
<%= page_nav_html %>
+<script id="pagination-data" type="application/json">
+<%=
+{
+ "next_page" => translate(locale, "Next page"),
+ "prev_page" => translate(locale, "Previous page"),
+ "is_rtl" => locale_is_rtl?(locale)
+}.to_pretty_json
+%>
+</script>
+
<script src="/js/watched_indicator.js"></script>