pagy 4.11.0 → 6.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/lib/config/pagy.rb +119 -58
- data/lib/javascripts/pagy-dev.js +114 -0
- data/lib/javascripts/pagy-module.d.ts +5 -0
- data/lib/javascripts/pagy-module.js +113 -0
- data/lib/javascripts/pagy.js +1 -121
- data/lib/locales/de.yml +1 -1
- data/lib/locales/ko.yml +1 -1
- data/lib/locales/nn.yml +22 -0
- data/lib/locales/ta.yml +22 -0
- data/lib/pagy/backend.rb +10 -13
- data/lib/pagy/calendar/day.rb +39 -0
- data/lib/pagy/calendar/helper.rb +61 -0
- data/lib/pagy/calendar/month.rb +40 -0
- data/lib/pagy/calendar/quarter.rb +47 -0
- data/lib/pagy/calendar/week.rb +39 -0
- data/lib/pagy/calendar/year.rb +33 -0
- data/lib/pagy/calendar.rb +100 -0
- data/lib/pagy/console.rb +6 -4
- data/lib/pagy/countless.rb +22 -23
- data/lib/pagy/exceptions.rb +14 -16
- data/lib/pagy/extras/arel.rb +11 -7
- data/lib/pagy/extras/array.rb +9 -9
- data/lib/pagy/extras/bootstrap.rb +45 -38
- data/lib/pagy/extras/bulma.rb +50 -38
- data/lib/pagy/extras/calendar.rb +49 -0
- data/lib/pagy/extras/countless.rb +15 -18
- data/lib/pagy/extras/elasticsearch_rails.rb +67 -48
- data/lib/pagy/extras/foundation.rb +39 -35
- data/lib/pagy/extras/frontend_helpers.rb +72 -0
- data/lib/pagy/extras/gearbox.rb +54 -0
- data/lib/pagy/extras/headers.rb +30 -20
- data/lib/pagy/extras/i18n.rb +15 -13
- data/lib/pagy/extras/items.rb +42 -40
- data/lib/pagy/extras/materialize.rb +40 -38
- data/lib/pagy/extras/meilisearch.rb +53 -44
- data/lib/pagy/extras/metadata.rb +15 -20
- data/lib/pagy/extras/navs.rb +35 -34
- data/lib/pagy/extras/overflow.rb +62 -61
- data/lib/pagy/extras/searchkick.rb +54 -46
- data/lib/pagy/extras/semantic.rb +42 -40
- data/lib/pagy/extras/standalone.rb +50 -46
- data/lib/pagy/extras/support.rb +24 -16
- data/lib/pagy/extras/trim.rb +15 -14
- data/lib/pagy/extras/uikit.rb +41 -38
- data/lib/pagy/frontend.rb +36 -59
- data/lib/pagy/i18n.rb +164 -0
- data/lib/pagy/url_helpers.rb +24 -0
- data/lib/pagy.rb +90 -31
- data/lib/templates/bootstrap_nav.html.erb +2 -2
- data/lib/templates/bootstrap_nav.html.haml +2 -2
- data/lib/templates/bootstrap_nav.html.slim +2 -2
- data/lib/templates/foundation_nav.html.erb +1 -1
- data/lib/templates/foundation_nav.html.haml +1 -1
- data/lib/templates/foundation_nav.html.slim +1 -1
- data/lib/templates/nav.html.erb +1 -1
- data/lib/templates/nav.html.haml +1 -1
- data/lib/templates/nav.html.slim +1 -1
- data/lib/templates/uikit_nav.html.erb +2 -2
- data/lib/templates/uikit_nav.html.haml +1 -1
- data/lib/templates/uikit_nav.html.slim +2 -2
- metadata +29 -13
- data/lib/locales/utils/i18n.rb +0 -17
- data/lib/locales/utils/loader.rb +0 -31
- data/lib/locales/utils/p11n.rb +0 -112
- data/lib/pagy/deprecation.rb +0 -27
- data/lib/pagy/extras/shared.rb +0 -52
data/lib/javascripts/pagy.js
CHANGED
@@ -1,121 +1 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
function Pagy(){}
|
4
|
-
|
5
|
-
Pagy.version = '4.11.0'
|
6
|
-
|
7
|
-
Pagy.delay = 100
|
8
|
-
|
9
|
-
Pagy.init =
|
10
|
-
function(arg) {
|
11
|
-
var target = arg instanceof Event || arg === undefined ? document : arg,
|
12
|
-
elements = target.querySelectorAll('[data-pagy-json]')
|
13
|
-
for (var i = 0, len = elements.length; i < len; i++) {
|
14
|
-
var args = JSON.parse(elements[i].getAttribute('data-pagy-json')),
|
15
|
-
fname = args.shift()
|
16
|
-
args.unshift(elements[i])
|
17
|
-
Pagy[fname].apply(null, args)
|
18
|
-
}
|
19
|
-
}
|
20
|
-
|
21
|
-
Pagy.nav =
|
22
|
-
function(pagyEl, tags, sequels, trimParam) {
|
23
|
-
var lastWidth,
|
24
|
-
pageREg = new RegExp(/__pagy_page__/g),
|
25
|
-
widths = []
|
26
|
-
for (var width in sequels) {
|
27
|
-
if (sequels.hasOwnProperty(width)) { widths.push(parseInt(width, 10)) }
|
28
|
-
}
|
29
|
-
widths.sort(function(a, b) { return b - a })
|
30
|
-
|
31
|
-
pagyEl.render =
|
32
|
-
function() {
|
33
|
-
var width, i, len
|
34
|
-
for (i = 0, len = widths.length; i < len; i++) {
|
35
|
-
if (this.parentElement.clientWidth > widths[i]) { width = widths[i]; break }
|
36
|
-
}
|
37
|
-
if (width !== lastWidth) {
|
38
|
-
var html = tags.before,
|
39
|
-
series = sequels[width]
|
40
|
-
for (i = 0, len = series.length; i < len; i++) {
|
41
|
-
var item = series[i]
|
42
|
-
if (typeof(trimParam) === 'string' && item === 1) { html += Pagy.trim(tags.link.replace(pageREg, item), trimParam) }
|
43
|
-
else if (typeof(item) === 'number') { html += tags.link.replace(pageREg, item) }
|
44
|
-
else if (item === 'gap') { html += tags.gap }
|
45
|
-
else if (typeof(item) === 'string') { html += tags.active.replace(pageREg, item) }
|
46
|
-
}
|
47
|
-
html += tags.after
|
48
|
-
this.innerHTML = ''
|
49
|
-
this.insertAdjacentHTML('afterbegin', html)
|
50
|
-
lastWidth = width
|
51
|
-
}
|
52
|
-
}.bind(pagyEl)
|
53
|
-
pagyEl.render()
|
54
|
-
}
|
55
|
-
|
56
|
-
Pagy.combo_nav =
|
57
|
-
function(pagyEl, page, link, trimParam) {
|
58
|
-
var input = pagyEl.getElementsByTagName('input')[0],
|
59
|
-
toPage =
|
60
|
-
function() {
|
61
|
-
if (page !== input.value) {
|
62
|
-
var html = link.replace(/__pagy_page__/, input.value)
|
63
|
-
if (typeof (trimParam) === 'string' && input.value === '1') { html = Pagy.trim(html, trimParam) }
|
64
|
-
pagyEl.insertAdjacentHTML('afterbegin', html)
|
65
|
-
pagyEl.getElementsByTagName('a')[0].click()
|
66
|
-
}
|
67
|
-
}
|
68
|
-
Pagy.addInputEventListeners(input, toPage)
|
69
|
-
}
|
70
|
-
|
71
|
-
Pagy.items_selector =
|
72
|
-
function(pagyEl, from, link, param) {
|
73
|
-
var input = pagyEl.getElementsByTagName('input')[0],
|
74
|
-
current = input.value,
|
75
|
-
toPage =
|
76
|
-
function() {
|
77
|
-
var items = input.value
|
78
|
-
if (current !== items) {
|
79
|
-
var page = Math.max(Math.ceil(from / items), 1),
|
80
|
-
html = link.replace(/__pagy_page__/, page).replace(/__pagy_items__/, items)
|
81
|
-
if (typeof (param) === 'string' && page === 1) { html = Pagy.trim(html, param) }
|
82
|
-
pagyEl.insertAdjacentHTML('afterbegin', html)
|
83
|
-
pagyEl.getElementsByTagName('a')[0].click()
|
84
|
-
}
|
85
|
-
}
|
86
|
-
Pagy.addInputEventListeners(input, toPage)
|
87
|
-
}
|
88
|
-
|
89
|
-
Pagy.addInputEventListeners =
|
90
|
-
function(input, toPage) {
|
91
|
-
// select the content on click: easier for typing a number
|
92
|
-
input.addEventListener('click', function() { this.select() })
|
93
|
-
// toPage when the input looses focus
|
94
|
-
input.addEventListener('focusout', toPage)
|
95
|
-
// … and when pressing enter inside the input
|
96
|
-
input.addEventListener('keyup', function(e) { if (e.which === 13) {toPage()} }.bind(this))
|
97
|
-
}
|
98
|
-
|
99
|
-
Pagy.trim =
|
100
|
-
function(html, param) {
|
101
|
-
var re = new RegExp('[?&]' + param + '=1\\b(?!&)|\\b' + param + '=1&')
|
102
|
-
return html.replace(re, '')
|
103
|
-
}
|
104
|
-
|
105
|
-
Pagy.renderNavs =
|
106
|
-
function() {
|
107
|
-
var navs = document.getElementsByClassName('pagy-njs') // 'pagy-njs' is common to all *nav_js helpers
|
108
|
-
for (var i = 0, len = navs.length; i < len; i++) { navs[i].render() }
|
109
|
-
}
|
110
|
-
|
111
|
-
Pagy.waitForMe =
|
112
|
-
function() {
|
113
|
-
if (typeof(Pagy.tid) === 'number') { clearTimeout(Pagy.tid) }
|
114
|
-
Pagy.tid = setTimeout(Pagy.renderNavs, Pagy.delay)
|
115
|
-
}
|
116
|
-
|
117
|
-
|
118
|
-
if (typeof window !== "undefined") {
|
119
|
-
window.addEventListener('resize', Pagy.waitForMe, true)
|
120
|
-
}
|
121
|
-
|
1
|
+
!function(){var e=(()=>{const e=new ResizeObserver((e=>e.forEach((e=>e.target.querySelectorAll(".pagy-rjs").forEach((e=>e.pagyRender())))))),t=(t,[n,r,a,c])=>{const i=t.parentElement??t,s=Object.keys(r).map((e=>parseInt(e))).sort(((e,t)=>t-e));let p=-1;const l=(e,t,n)=>e.replace(/__pagy_page__/g,t).replace(/__pagy_label__/g,n);(t.pagyRender=function(){const e=s.find((e=>e<i.clientWidth))||0;if(e===p)return;let g=n.before;const y=r[e.toString()],f=a?.[e.toString()]??y.map((e=>e.toString()));for(const e in y){const t=y[e],r=f[e];g+="string"==typeof c&&1===t?o(l(n.link,t.toString(),r),c):"number"==typeof t?l(n.link,t.toString(),r):"gap"===t?n.gap:l(n.active,t,r)}g+=n.after,t.innerHTML="",t.insertAdjacentHTML("afterbegin",g),p=e})(),t.classList.contains("pagy-rjs")&&e.observe(i)},n=(e,[t,n])=>a(e,(e=>[e,t.replace(/__pagy_page__/,e)]),n),r=(e,[t,n,r])=>{a(e,(e=>{const r=Math.max(Math.ceil(t/parseInt(e)),1).toString();return[r,n.replace(/__pagy_page__/,r).replace(/__pagy_items__/,e)]}),r)},a=(e,t,n)=>{const r=e.querySelector("input"),a=r.value,c=function(){if(r.value===a)return;const[c,i,s]=[r.min,r.value,r.max].map((e=>parseInt(e)||0));if(i<c||i>s)return r.value=a,void r.select();let[p,l]=t(r.value);"string"==typeof n&&"1"===p&&(l=o(l,n)),e.insertAdjacentHTML("afterbegin",l),e.querySelector("a").click()};["change","focus"].forEach((e=>r.addEventListener(e,r.select))),r.addEventListener("focusout",c),r.addEventListener("keypress",(e=>{"Enter"===e.key&&c()}))},o=(e,t)=>e.replace(new RegExp(`(\\?|&)${t}=1\\b(?!&)|\\b${t}=1&`),"");return{version:"6.0.0",init(e){const a=(e instanceof Element?e:document).querySelectorAll("[data-pagy]");for(const e of a)try{const a=Uint8Array.from(atob(e.getAttribute("data-pagy")),(e=>e.charCodeAt(0))),[o,...c]=JSON.parse((new TextDecoder).decode(a));"nav"===o?t(e,c):"combo"===o?n(e,c):"selector"===o?r(e,c):console.warn("Skipped Pagy.init() for: %o\nUnknown keyword '%s'",e,o)}catch(t){console.warn("Skipped Pagy.init() for: %o\n%s",e,t)}}}})();window.Pagy=e}();
|
data/lib/locales/de.yml
CHANGED
@@ -13,7 +13,7 @@ de:
|
|
13
13
|
gap: "…"
|
14
14
|
|
15
15
|
info:
|
16
|
-
no_items: "
|
16
|
+
no_items: "Keine %{item_name} gefunden"
|
17
17
|
single_page: "Zeige <b>%{count}</b> %{item_name}"
|
18
18
|
multiple_pages: "Zeige %{item_name} <b>%{from}-%{to}</b> von <b>%{count}</b> gesamt"
|
19
19
|
|
data/lib/locales/ko.yml
CHANGED
data/lib/locales/nn.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# :one_other pluralization (see https://github.com/ddnexus/pagy/blob/master/lib/locales/utils/p11n.rb)
|
2
|
+
|
3
|
+
nn:
|
4
|
+
pagy:
|
5
|
+
|
6
|
+
item_name:
|
7
|
+
one: "resultat"
|
8
|
+
other: "resultat"
|
9
|
+
|
10
|
+
nav:
|
11
|
+
prev: "‹ Førre"
|
12
|
+
next: "Neste ›"
|
13
|
+
gap: "…"
|
14
|
+
|
15
|
+
info:
|
16
|
+
no_items: "Ingen %{item_name} funne"
|
17
|
+
single_page: "Viser <b>%{count}</b> %{item_name}"
|
18
|
+
multiple_pages: "Viser %{item_name} <b>%{from}-%{to}</b> av totalt <b>%{count}</b>"
|
19
|
+
|
20
|
+
combo_nav_js: "<label>Side %{page_input} av %{pages}</label>"
|
21
|
+
|
22
|
+
items_selector_js: "<label>Vis %{items_input} %{item_name} per side</label>"
|
data/lib/locales/ta.yml
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# :one_other pluralization (see https://github.com/ddnexus/pagy/blob/master/lib/locales/utils/p11n.rb)
|
2
|
+
|
3
|
+
ta:
|
4
|
+
pagy:
|
5
|
+
|
6
|
+
item_name:
|
7
|
+
one: "பதிவு"
|
8
|
+
other: "பதிவுகள்"
|
9
|
+
|
10
|
+
nav:
|
11
|
+
prev: "‹ முந்தையது"
|
12
|
+
next: "அடுத்தது ›"
|
13
|
+
gap: "…"
|
14
|
+
|
15
|
+
info:
|
16
|
+
no_items: "%{item_name} கிடைக்கவில்லை"
|
17
|
+
single_page: "<b>%{count}</b> %{item_name} காட்டப்படுகின்றது"
|
18
|
+
multiple_pages: "மொத்தம் <b>%{count}</b> %{item_name}, காட்டப்படுபவை <b>%{from}-%{to}</b>"
|
19
|
+
|
20
|
+
combo_nav_js: "<label>%{pages}-இல் %{page_input}-வது பக்கம்</label>"
|
21
|
+
|
22
|
+
items_selector_js: "<label>ஒரு பக்கத்திற்கு %{items_input} %{item_name} காட்டு</label>"
|
data/lib/pagy/backend.rb
CHANGED
@@ -2,34 +2,31 @@
|
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
4
|
class Pagy
|
5
|
-
#
|
5
|
+
# Define a few generic methods to paginate an ORM collection out of the box,
|
6
6
|
# or any collection by overriding pagy_get_items and/or pagy_get_vars in your controller
|
7
|
-
|
8
7
|
# See also the extras if you need specialized methods to paginate Arrays or other collections
|
9
|
-
|
10
|
-
|
11
8
|
module Backend
|
12
|
-
private
|
9
|
+
private
|
13
10
|
|
14
|
-
# Return Pagy object and items
|
15
|
-
def pagy(collection, vars={})
|
11
|
+
# Return Pagy object and paginated items/results
|
12
|
+
def pagy(collection, vars = {})
|
16
13
|
pagy = Pagy.new(pagy_get_vars(collection, vars))
|
17
|
-
[
|
14
|
+
[pagy, pagy_get_items(collection, pagy)]
|
18
15
|
end
|
19
16
|
|
20
17
|
# Sub-method called only by #pagy: here for easy customization of variables by overriding
|
18
|
+
# You may need to override the count call for non AR collections
|
21
19
|
def pagy_get_vars(collection, vars)
|
22
|
-
pagy_set_items_from_params(vars) if defined?(
|
23
|
-
vars[:count] ||= (
|
24
|
-
vars[:page] ||= params[
|
20
|
+
pagy_set_items_from_params(vars) if defined?(ItemsExtra)
|
21
|
+
vars[:count] ||= (count = collection.count(:all)).is_a?(Hash) ? count.size : count
|
22
|
+
vars[:page] ||= params[vars[:page_param] || DEFAULT[:page_param]]
|
25
23
|
vars
|
26
24
|
end
|
27
25
|
|
28
26
|
# Sub-method called only by #pagy: here for easy customization of record-extraction by overriding
|
27
|
+
# You may need to override this method for collections without offset|limit
|
29
28
|
def pagy_get_items(collection, pagy)
|
30
|
-
# This should work with ActiveRecord, Sequel, Mongoid...
|
31
29
|
collection.offset(pagy.offset).limit(pagy.items)
|
32
30
|
end
|
33
|
-
|
34
31
|
end
|
35
32
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/api/calendar
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class Pagy # :nodoc:
|
5
|
+
class Calendar # :nodoc:
|
6
|
+
# Calendar day subclass
|
7
|
+
class Day < Calendar
|
8
|
+
DEFAULT = { order: :asc, # rubocop:disable Style/MutableConstant
|
9
|
+
format: '%Y-%m-%d' }
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
# Setup the calendar variables
|
14
|
+
def setup_unit_vars
|
15
|
+
super
|
16
|
+
@initial = @starting.beginning_of_day
|
17
|
+
@final = @ending.tomorrow.beginning_of_day
|
18
|
+
@pages = @last = page_offset(@initial, @final)
|
19
|
+
@from = starting_time_for(@page)
|
20
|
+
@to = @from.tomorrow
|
21
|
+
end
|
22
|
+
|
23
|
+
# Starting time for the page
|
24
|
+
def starting_time_for(page)
|
25
|
+
@initial.days_since(time_offset_for(page))
|
26
|
+
end
|
27
|
+
|
28
|
+
def page_offset_at(time)
|
29
|
+
page_offset(@initial, time.beginning_of_day)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def page_offset(time_a, time_b) # remove in 6.0
|
35
|
+
(time_b.time - time_a.time).to_i / 1.day
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Pagy # :nodoc:
|
4
|
+
class Calendar # :nodoc:
|
5
|
+
# Initializes the calendar objects, reducing complexity in the extra
|
6
|
+
# The returned calendar is a simple hash of units/objects with an added helper
|
7
|
+
# returning the last_object_at(time) used in the extra
|
8
|
+
class Helper < Hash
|
9
|
+
class << self
|
10
|
+
private
|
11
|
+
|
12
|
+
def init(conf, period, params)
|
13
|
+
new.send(:init, conf, period, params)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def init(conf, period, params)
|
20
|
+
@units = Calendar::UNITS & conf.keys # get the units in time length desc order
|
21
|
+
raise ArgumentError, 'no calendar unit found in pagy_calendar @configuration' if @units.empty?
|
22
|
+
|
23
|
+
@period = period
|
24
|
+
@params = params
|
25
|
+
@page_param = conf[:pagy][:page_param] || DEFAULT[:page_param]
|
26
|
+
@conf = Marshal.load(Marshal.dump(conf)) # store a copy
|
27
|
+
@units.each do |unit| # set all the :page_param vars for later deletion
|
28
|
+
unit_page_param = :"#{unit}_#{@page_param}"
|
29
|
+
conf[unit][:page_param] = unit_page_param
|
30
|
+
conf[unit][:page] = @params[unit_page_param]
|
31
|
+
end
|
32
|
+
calendar = {}
|
33
|
+
object = nil
|
34
|
+
@units.each_with_index do |unit, index|
|
35
|
+
params_to_delete = @units[(index + 1), @units.size].map { |sub| conf[sub][:page_param] } + [@page_param]
|
36
|
+
conf[unit][:params] = lambda do |unit_params| # delete page_param from the sub-units
|
37
|
+
# Hash#except missing from ruby 2.5 baseline
|
38
|
+
params_to_delete.each { |p| unit_params.delete(p.to_s) }
|
39
|
+
unit_params
|
40
|
+
end
|
41
|
+
conf[unit][:period] = object&.send(:active_period) || @period
|
42
|
+
calendar[unit] = object = Calendar.send(:create, unit, conf[unit])
|
43
|
+
end
|
44
|
+
[replace(calendar), object.from, object.to]
|
45
|
+
end
|
46
|
+
|
47
|
+
def last_object_at(time)
|
48
|
+
conf = Marshal.load(Marshal.dump(@conf))
|
49
|
+
page_params = {}
|
50
|
+
@units.inject(nil) do |object, unit|
|
51
|
+
conf[unit][:period] = object&.send(:active_period) || @period
|
52
|
+
conf[unit][:page] = page_params[:"#{unit}_#{@page_param}"] \
|
53
|
+
= Calendar.send(:create, unit, conf[unit]).send(:page_at, time)
|
54
|
+
conf[unit][:params] ||= {}
|
55
|
+
conf[unit][:params].merge!(page_params)
|
56
|
+
Calendar.send(:create, unit, conf[unit])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/api/calendar
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class Pagy # :nodoc:
|
5
|
+
class Calendar # :nodoc:
|
6
|
+
# Calendar month subclass
|
7
|
+
class Month < Calendar
|
8
|
+
DEFAULT = { order: :asc, # rubocop:disable Style/MutableConstant
|
9
|
+
format: '%Y-%m' }
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
# Setup the calendar variables
|
14
|
+
def setup_unit_vars
|
15
|
+
super
|
16
|
+
@initial = @starting.beginning_of_month
|
17
|
+
@final = @ending.next_month.beginning_of_month
|
18
|
+
@pages = @last = (months_in(@final) - months_in(@initial))
|
19
|
+
@from = starting_time_for(@page)
|
20
|
+
@to = @from.next_month
|
21
|
+
end
|
22
|
+
|
23
|
+
# Starting time for the page
|
24
|
+
def starting_time_for(page)
|
25
|
+
@initial.months_since(time_offset_for(page))
|
26
|
+
end
|
27
|
+
|
28
|
+
def page_offset_at(time)
|
29
|
+
months_in(time.beginning_of_month) - months_in(@initial)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# Number of months in time
|
35
|
+
def months_in(time)
|
36
|
+
(time.year * 12) + time.month
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/api/calendar
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class Pagy # :nodoc:
|
5
|
+
class Calendar # :nodoc:
|
6
|
+
# Calendar quarter subclass
|
7
|
+
class Quarter < Calendar
|
8
|
+
DEFAULT = { order: :asc, # rubocop:disable Style/MutableConstant
|
9
|
+
format: '%Y-Q%q' } # '%q' token
|
10
|
+
|
11
|
+
# The label for any page, with the substitution of the '%q' token
|
12
|
+
def label_for(page, opts = {})
|
13
|
+
starting_time = starting_time_for(page.to_i) # page could be a string
|
14
|
+
opts[:format] = (opts[:format] || @vars[:format]).gsub('%q') { (starting_time.month / 3.0).ceil }
|
15
|
+
localize(starting_time, opts)
|
16
|
+
end
|
17
|
+
|
18
|
+
protected
|
19
|
+
|
20
|
+
# Setup the calendar variables
|
21
|
+
def setup_unit_vars
|
22
|
+
super
|
23
|
+
@initial = @starting.beginning_of_quarter
|
24
|
+
@final = @ending.next_quarter.beginning_of_quarter
|
25
|
+
@pages = @last = (months_in(@final) - months_in(@initial)) / 3
|
26
|
+
@from = starting_time_for(@page)
|
27
|
+
@to = @from.next_quarter
|
28
|
+
end
|
29
|
+
|
30
|
+
# Starting time for the page
|
31
|
+
def starting_time_for(page)
|
32
|
+
@initial.months_since(time_offset_for(page) * 3)
|
33
|
+
end
|
34
|
+
|
35
|
+
def page_offset_at(time)
|
36
|
+
(months_in(time.beginning_of_quarter) - months_in(@initial)) / 3
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
# Number of months in time
|
42
|
+
def months_in(time)
|
43
|
+
(time.year * 12) + time.month
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/api/calendar
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class Pagy # :nodoc:
|
5
|
+
class Calendar # :nodoc:
|
6
|
+
# Calendar week subclass
|
7
|
+
class Week < Calendar
|
8
|
+
DEFAULT = { order: :asc, # rubocop:disable Style/MutableConstant
|
9
|
+
format: '%Y-%W' }
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
# Setup the calendar variables
|
14
|
+
def setup_unit_vars
|
15
|
+
super
|
16
|
+
@initial = @starting.beginning_of_week
|
17
|
+
@final = @ending.next_week.beginning_of_week
|
18
|
+
@pages = @last = page_offset(@initial, @final)
|
19
|
+
@from = starting_time_for(@page)
|
20
|
+
@to = @from.next_week
|
21
|
+
end
|
22
|
+
|
23
|
+
# Starting time for the page
|
24
|
+
def starting_time_for(page)
|
25
|
+
@initial.weeks_since(time_offset_for(page))
|
26
|
+
end
|
27
|
+
|
28
|
+
def page_offset_at(time)
|
29
|
+
page_offset(@initial, time.beginning_of_week)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def page_offset(time_a, time_b) # remove in 6.0
|
35
|
+
(time_b.time - time_a.time).to_i / 1.week
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/api/calendar
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
class Pagy # :nodoc:
|
5
|
+
class Calendar # :nodoc:
|
6
|
+
# Calendar year subclass
|
7
|
+
class Year < Calendar
|
8
|
+
DEFAULT = { order: :asc, # rubocop:disable Style/MutableConstant
|
9
|
+
format: '%Y' }
|
10
|
+
|
11
|
+
protected
|
12
|
+
|
13
|
+
# Setup the calendar variables
|
14
|
+
def setup_unit_vars
|
15
|
+
super
|
16
|
+
@initial = @starting.beginning_of_year
|
17
|
+
@final = @ending.next_year.beginning_of_year
|
18
|
+
@pages = @last = @final.year - @initial.year
|
19
|
+
@from = starting_time_for(@page)
|
20
|
+
@to = @from.next_year
|
21
|
+
end
|
22
|
+
|
23
|
+
# Starting time for the page
|
24
|
+
def starting_time_for(page)
|
25
|
+
@initial.years_since(time_offset_for(page))
|
26
|
+
end
|
27
|
+
|
28
|
+
def page_offset_at(time)
|
29
|
+
time.beginning_of_year.year - @initial.year
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/api/calendar
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'active_support'
|
5
|
+
require 'active_support/core_ext/time'
|
6
|
+
require 'active_support/core_ext/date_and_time/calculations'
|
7
|
+
require 'active_support/core_ext/numeric/time'
|
8
|
+
require 'active_support/core_ext/integer/time'
|
9
|
+
|
10
|
+
require 'pagy'
|
11
|
+
|
12
|
+
class Pagy # :nodoc:
|
13
|
+
# Base class for time units subclasses (Year, Quarter, Month, Week, Day)
|
14
|
+
class Calendar < Pagy
|
15
|
+
# Specific out of range error
|
16
|
+
class OutOfRangeError < StandardError; end
|
17
|
+
|
18
|
+
# List of units in desc order of duration. It can be used for custom units.
|
19
|
+
UNITS = %i[year quarter month week day] # rubocop:disable Style/MutableConstant
|
20
|
+
|
21
|
+
attr_reader :order
|
22
|
+
|
23
|
+
# Merge and validate the options, do some simple arithmetic and set a few instance variables
|
24
|
+
def initialize(vars) # rubocop:disable Lint/MissingSuper
|
25
|
+
raise InternalError, 'Pagy::Calendar is a base class; use one of its subclasses' if instance_of?(Pagy::Calendar)
|
26
|
+
|
27
|
+
vars = self.class::DEFAULT.merge(vars) # subclass specific default
|
28
|
+
normalize_vars(vars) # general default
|
29
|
+
setup_vars(page: 1)
|
30
|
+
setup_unit_vars
|
31
|
+
setup_params_var
|
32
|
+
raise OverflowError.new(self, :page, "in 1..#{@last}", @page) if @page > @last
|
33
|
+
|
34
|
+
@prev = (@page - 1 unless @page == 1)
|
35
|
+
@next = @page == @last ? (1 if @vars[:cycle]) : @page + 1
|
36
|
+
end
|
37
|
+
|
38
|
+
# The label for the current page (it can pass along the I18n gem opts when it's used with the i18n extra)
|
39
|
+
def label(opts = {})
|
40
|
+
label_for(@page, opts)
|
41
|
+
end
|
42
|
+
|
43
|
+
# The label for any page (it can pass along the I18n gem opts when it's used with the i18n extra)
|
44
|
+
def label_for(page, opts = {})
|
45
|
+
opts[:format] ||= @vars[:format]
|
46
|
+
localize(starting_time_for(page.to_i), opts) # page could be a string
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
|
51
|
+
# The page that includes time
|
52
|
+
def page_at(time)
|
53
|
+
raise OutOfRangeError unless time.between?(@initial, @final)
|
54
|
+
|
55
|
+
offset = page_offset_at(time) # offset starts from 0
|
56
|
+
@order == :asc ? offset + 1 : @pages - offset
|
57
|
+
end
|
58
|
+
|
59
|
+
# Base class method for the setup of the unit variables (subclasses must implement it and call super)
|
60
|
+
def setup_unit_vars
|
61
|
+
raise VariableError.new(self, :format, 'to be a strftime format', @vars[:format]) unless @vars[:format].is_a?(String)
|
62
|
+
raise VariableError.new(self, :order, 'to be in [:asc, :desc]', @order) \
|
63
|
+
unless %i[asc desc].include?(@order = @vars[:order])
|
64
|
+
|
65
|
+
@starting, @ending = @vars[:period]
|
66
|
+
raise VariableError.new(self, :period, 'to be a an Array of min and max TimeWithZone instances', @vars[:period]) \
|
67
|
+
unless @starting.is_a?(ActiveSupport::TimeWithZone) \
|
68
|
+
&& @ending.is_a?(ActiveSupport::TimeWithZone) && @starting <= @ending
|
69
|
+
end
|
70
|
+
|
71
|
+
# Apply the strftime format to the time (overridden by the i18n extra when localization is required)
|
72
|
+
def localize(time, opts)
|
73
|
+
time.strftime(opts[:format])
|
74
|
+
end
|
75
|
+
|
76
|
+
# Number of time units to offset from the @initial time, in order to get the ordered starting time for the page.
|
77
|
+
# Used in starting_time_for(page) where page starts from 1 (e.g. page to starting_time means subtracting 1)
|
78
|
+
def time_offset_for(page)
|
79
|
+
@order == :asc ? page - 1 : @pages - page
|
80
|
+
end
|
81
|
+
|
82
|
+
# Period of the active page (used internally for nested units)
|
83
|
+
def active_period
|
84
|
+
[[@starting, @from].max, [@to - 1, @ending].min] # -1 sec: include only last unit day
|
85
|
+
end
|
86
|
+
|
87
|
+
class << self
|
88
|
+
# Create a subclass instance by unit name (internal use)
|
89
|
+
def create(unit, vars)
|
90
|
+
raise InternalError, "unit must be in #{UNITS.inspect}; got #{unit}" unless UNITS.include?(unit)
|
91
|
+
|
92
|
+
name = unit.to_s
|
93
|
+
name[0] = name[0].capitalize
|
94
|
+
Object.const_get("Pagy::Calendar::#{name}").new(vars)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
# Require the subclass files in UNITS (no custom unit at this point yet)
|
99
|
+
Calendar::UNITS.each { |unit| require "pagy/calendar/#{unit}" }
|
100
|
+
end
|
data/lib/pagy/console.rb
CHANGED
@@ -1,21 +1,23 @@
|
|
1
|
+
# See Pagy::Console API documentation: https://ddnexus.github.io/pagy/api/console
|
1
2
|
# frozen_string_literal: true
|
2
3
|
|
3
4
|
require 'pagy' # so you can require just the extra in the console
|
4
5
|
require 'pagy/extras/standalone'
|
5
6
|
|
6
7
|
class Pagy
|
7
|
-
#
|
8
|
+
# Provide a ready to use pagy environment when included in irb/rails console
|
8
9
|
module Console
|
10
|
+
# Include Backend, Frontend and set the default URL
|
9
11
|
def self.included(main)
|
10
12
|
main.include(Backend)
|
11
13
|
main.include(Frontend)
|
12
|
-
|
14
|
+
DEFAULT[:url] = 'http://www.example.com/subdir'
|
13
15
|
end
|
14
16
|
|
17
|
+
# Require the extras passed as arguments
|
15
18
|
def pagy_extras(*extras)
|
16
|
-
extras.each {|extra| require "pagy/extras/#{extra}"}
|
19
|
+
extras.each { |extra| require "pagy/extras/#{extra}" }
|
17
20
|
puts "Required extras: #{extras.map(&:inspect).join(', ')}"
|
18
21
|
end
|
19
22
|
end
|
20
|
-
|
21
23
|
end
|