Skip to content

Commit

Permalink
WIP: Manage new tab/window and reset for pasting in other browser;
Browse files Browse the repository at this point in the history
nav_js ready
  • Loading branch information
ddnexus committed Dec 22, 2024
1 parent 145b5ff commit 901bd0e
Show file tree
Hide file tree
Showing 11 changed files with 157 additions and 75 deletions.
4 changes: 2 additions & 2 deletions gem/javascripts/pagy.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions gem/javascripts/pagy.min.js.map

Large diffs are not rendered by default.

48 changes: 35 additions & 13 deletions gem/javascripts/pagy.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
const Pagy = (() => {
const rjsObserver = new ResizeObserver((entries) => entries.forEach((e) => e.target.querySelectorAll(".pagy-rjs").forEach((el) => el.pagyRender())));
const sS = sessionStorage;
const sync = new BroadcastChannel("pagy");
const tabId = Date.now();
sync.addEventListener("message", (e) => {
if (e.data.from) {
const cutoffs = sS.getItem(e.data.key);
if (cutoffs) {
sync.postMessage({ to: e.data.from, key: e.data.key, cutoffs });
}
} else if (e.data.to) {
if (e.data.to == tabId) {
sS.setItem(e.data.key, e.data.cutoffs);
}
}
});
const rjsObserver = new ResizeObserver((entries) => entries.forEach((e) => {
e.target.querySelectorAll(".pagy-rjs").forEach((el) => el.pagyRender());
e.target.querySelectorAll(".pagy-keyset").forEach((el) => el.completeUrls());
}));
const b64 = {
encode: (unicode) => btoa(String.fromCharCode(...new TextEncoder().encode(unicode))),
toSafe: (unsafe) => unsafe.replace(/=/g, "").replace(/[+/]/g, (match) => match == "+" ? "-" : "_"),
Expand All @@ -9,35 +27,39 @@ const Pagy = (() => {
const initNav = (el, [opts]) => {
initCutoff(el, opts);
};
const initCutoff = (el, opts) => {
const initCutoff = async (el, opts) => {
if (!opts || !Array.isArray(opts.update) || !opts.cutoffs_param || !opts.page_param) {
return;
}
history.replaceState(null, "", location.href.replace(RegExp(`&?${opts.cutoffs_param}=.*\$`), ""));
let [key, latest] = opts.update;
const pagyId = document.cookie.split(/;\s+/).find((row) => row.startsWith("pagy="))?.split("=")[1] || Math.floor(Math.random() * 36 ** 3).toString(36);
document.cookie = "pagy=" + pagyId;
let [key, last, latest] = opts.update;
if (key && !(key in sS)) {
sync.postMessage({ from: tabId, key });
await new Promise((resolve) => setTimeout(() => resolve(""), 100));
}
key ||= "pagy-" + Date.now().toString(36);
const cs = sessionStorage.getItem(key);
const cs = sS.getItem(key);
const cutoffs = cs ? JSON.parse(cs) : [null];
if (latest) {
cutoffs.push(latest);
sessionStorage.setItem(key, JSON.stringify(cutoffs));
if (last && latest) {
cutoffs[last] = latest;
sS.setItem(key, JSON.stringify(cutoffs));
}
el.addEventListener("click", (e) => {
const a = e.target;
if (a && a.nodeName == "A" && a.href.length > 0) {
(el.completeUrls = () => {
for (const a of el.querySelectorAll("a[href]")) {
const url = a.href;
const re = new RegExp(`(?<=\\?.*)\\b${opts.page_param}=([\\d]+)`);
const page = parseInt(url.match(re)?.[1]);
const value = b64.safeEncode(JSON.stringify([
pagyId,
key,
cutoffs.length,
cutoffs[page - 1],
cutoffs[page]
]));
a.href = url + `&${opts.cutoffs_param}=${value}`;
console.warn("listener run");
}
});
})();
};
const initNavJs = (el, [tokens, sequels, labelSequels, opts]) => {
const container = el.parentElement ?? el;
Expand Down
3 changes: 2 additions & 1 deletion gem/lib/pagy/extras/bootstrap.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ def pagy_bootstrap_nav_js(pagy, id: nil, classes: 'pagination', aria_label: nil,
pagy_t('pagy.gap')}</a></li>),
'after' => %(#{bootstrap_next_html pagy, a}</ul>) }

%(<nav#{id} class="#{'pagy-rjs ' if sequels.size > 1}pagy-bootstrap nav-js" #{
%(<nav#{id} class="#{'pagy-rjs ' if sequels.size > 1}#{
'pagy-keyset ' if defined?(::Pagy::KeysetForUI) && Pagy.is_a?(KeysetForUI)}pagy-bootstrap nav-js" #{
nav_aria_label(pagy, aria_label:)} #{
pagy_data(pagy, :nav_js, tokens, sequels, pagy.label_sequels(sequels))
}></nav>)
Expand Down
3 changes: 2 additions & 1 deletion gem/lib/pagy/extras/bulma.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ def pagy_bulma_nav_js(pagy, id: nil, classes: 'pagy-bulma nav-js pagination is-c
'gap' => %(<li><span class="pagination-ellipsis">#{pagy_t 'pagy.gap'}</span></li>),
'after' => '</ul>' }

%(<nav#{id} class="#{'pagy-rjs ' if sequels.size > 1}#{classes}" #{
%(<nav#{id} class="#{'pagy-rjs ' if sequels.size > 1}#{
'pagy-keyset ' if defined?(::Pagy::KeysetForUI) && Pagy.is_a?(KeysetForUI)}#{classes}" #{
nav_aria_label(pagy, aria_label:)} #{
pagy_data(pagy, :nav_js, tokens, sequels, pagy.label_sequels(sequels))
}></nav>)
Expand Down
18 changes: 14 additions & 4 deletions gem/lib/pagy/extras/keyset_for_ui.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,23 @@ def pagy_keyset_for_ui(set, **vars)
vars[:limit] ||= pagy_get_limit(vars)
vars[:cutoffs_param] ||= DEFAULT[:cutoffs_param]
vars[:params] ||= ->(params) { params.tap { |p| p.delete(vars[:cutoffs_param].to_s) } }
vars[:cutoffs] ||= begin
cutoffs = params[vars[:cutoffs_param]]
JSON.parse(B64.urlsafe_decode(cutoffs)) if cutoffs
end
vars[:cutoffs] ||= get_cutoffs(vars)
pagy = KeysetForUI.new(set, **vars)
[pagy, pagy.records]
end

def get_cutoffs(vars)
cutoffs = params[vars[:cutoffs_param]] || return

cutoffs = JSON.parse(B64.urlsafe_decode(cutoffs))
pagy_id = cutoffs.shift
return cutoffs if request.cookies['pagy'] == pagy_id

# The url has been requested from another browser, which does not have the same sessionStorage,
# hence we need to restart the pagination to page 1
vars[:page] = 1
KeysetForUI::FIRST_PAGE
end
end
Backend.prepend KeysetForUIExtra

Expand Down
3 changes: 2 additions & 1 deletion gem/lib/pagy/extras/pagy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ def pagy_nav_js(pagy, id: nil, aria_label: nil, **vars)
'gap' => %(<a class="gap" role="link" aria-disabled="true">#{pagy_t('pagy.gap')}</a>),
'after' => next_a(pagy, a) }

%(<nav#{id} class="#{'pagy-rjs ' if sequels.size > 1}pagy nav-js" #{
%(<nav#{id} class="#{'pagy-rjs ' if sequels.size > 1}#{
'pagy-keyset ' if defined?(::Pagy::KeysetForUI) && Pagy.is_a?(KeysetForUI)}pagy nav-js" #{
nav_aria_label(pagy, aria_label:)} #{
pagy_data(pagy, :nav_js, tokens, sequels, pagy.label_sequels(sequels))
}></nav>)
Expand Down
12 changes: 4 additions & 8 deletions gem/lib/pagy/keyset_for_ui.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class Sequel < KeysetForUI

# Avoid args conflicts in composite SQL fragments
CUTOFF_PREFIX = 'cutoff_' # Prefix for cutoff_args
FIRST_PAGE = [nil, 1, nil, nil].freeze

include SharedUIMethods
attr_reader :update
Expand All @@ -31,13 +32,8 @@ def initialize(set, **vars)

# Get the cutoff from the client
def assign_cutoffs
beginning_page = [nil, 1, nil, nil]
# key, is from the client and sent back as-is in order to id the requests of the same set
key, @last, @prev_cutoff, @cutoff = @vars[:cutoffs] || beginning_page
if @page > @last
key, @last, @prev_cutoff, @cutoff = beginning_page
@page = 1
end
key, @last, @prev_cutoff, @cutoff = @vars[:cutoffs] || FIRST_PAGE
@update = [key]
# raise OverflowError.new(self, :page, "in 1..#{@last}", @page) if @page > @last
end
Expand Down Expand Up @@ -138,8 +134,8 @@ def next
@next ||= (@page + 1).tap do
unless @cutoff
@cutoff = derive_cutoff
@update << @cutoff # operation arguments for the client cutoffs
@last += 1 # reflect the added cutoff
@update.push(@last, @cutoff) # operation arguments for the client cutoffs
@last += 1 # reflect the added cutoff
end
end
end
Expand Down
Loading

0 comments on commit 901bd0e

Please sign in to comment.