pagy 4.11.0 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/lib/config/pagy.rb +119 -58
  4. data/lib/javascripts/pagy-dev.js +114 -0
  5. data/lib/javascripts/pagy-module.d.ts +5 -0
  6. data/lib/javascripts/pagy-module.js +113 -0
  7. data/lib/javascripts/pagy.js +1 -121
  8. data/lib/locales/de.yml +1 -1
  9. data/lib/locales/ko.yml +1 -1
  10. data/lib/locales/nn.yml +22 -0
  11. data/lib/locales/ta.yml +22 -0
  12. data/lib/pagy/backend.rb +10 -13
  13. data/lib/pagy/calendar/day.rb +39 -0
  14. data/lib/pagy/calendar/helper.rb +61 -0
  15. data/lib/pagy/calendar/month.rb +40 -0
  16. data/lib/pagy/calendar/quarter.rb +47 -0
  17. data/lib/pagy/calendar/week.rb +39 -0
  18. data/lib/pagy/calendar/year.rb +33 -0
  19. data/lib/pagy/calendar.rb +100 -0
  20. data/lib/pagy/console.rb +6 -4
  21. data/lib/pagy/countless.rb +22 -23
  22. data/lib/pagy/exceptions.rb +14 -16
  23. data/lib/pagy/extras/arel.rb +11 -7
  24. data/lib/pagy/extras/array.rb +9 -9
  25. data/lib/pagy/extras/bootstrap.rb +45 -38
  26. data/lib/pagy/extras/bulma.rb +50 -38
  27. data/lib/pagy/extras/calendar.rb +49 -0
  28. data/lib/pagy/extras/countless.rb +15 -18
  29. data/lib/pagy/extras/elasticsearch_rails.rb +67 -48
  30. data/lib/pagy/extras/foundation.rb +39 -35
  31. data/lib/pagy/extras/frontend_helpers.rb +72 -0
  32. data/lib/pagy/extras/gearbox.rb +54 -0
  33. data/lib/pagy/extras/headers.rb +30 -20
  34. data/lib/pagy/extras/i18n.rb +15 -13
  35. data/lib/pagy/extras/items.rb +42 -40
  36. data/lib/pagy/extras/materialize.rb +40 -38
  37. data/lib/pagy/extras/meilisearch.rb +53 -44
  38. data/lib/pagy/extras/metadata.rb +15 -20
  39. data/lib/pagy/extras/navs.rb +35 -34
  40. data/lib/pagy/extras/overflow.rb +62 -61
  41. data/lib/pagy/extras/searchkick.rb +54 -46
  42. data/lib/pagy/extras/semantic.rb +42 -40
  43. data/lib/pagy/extras/standalone.rb +50 -46
  44. data/lib/pagy/extras/support.rb +24 -16
  45. data/lib/pagy/extras/trim.rb +15 -14
  46. data/lib/pagy/extras/uikit.rb +41 -38
  47. data/lib/pagy/frontend.rb +36 -59
  48. data/lib/pagy/i18n.rb +164 -0
  49. data/lib/pagy/url_helpers.rb +24 -0
  50. data/lib/pagy.rb +90 -31
  51. data/lib/templates/bootstrap_nav.html.erb +2 -2
  52. data/lib/templates/bootstrap_nav.html.haml +2 -2
  53. data/lib/templates/bootstrap_nav.html.slim +2 -2
  54. data/lib/templates/foundation_nav.html.erb +1 -1
  55. data/lib/templates/foundation_nav.html.haml +1 -1
  56. data/lib/templates/foundation_nav.html.slim +1 -1
  57. data/lib/templates/nav.html.erb +1 -1
  58. data/lib/templates/nav.html.haml +1 -1
  59. data/lib/templates/nav.html.slim +1 -1
  60. data/lib/templates/uikit_nav.html.erb +2 -2
  61. data/lib/templates/uikit_nav.html.haml +1 -1
  62. data/lib/templates/uikit_nav.html.slim +2 -2
  63. metadata +29 -13
  64. data/lib/locales/utils/i18n.rb +0 -17
  65. data/lib/locales/utils/loader.rb +0 -31
  66. data/lib/locales/utils/p11n.rb +0 -112
  67. data/lib/pagy/deprecation.rb +0 -27
  68. data/lib/pagy/extras/shared.rb +0 -52
@@ -1,121 +1 @@
1
- // See the Pagy documentation: https://ddnexus.github.io/pagy/extras#javascript
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(`(\\?|&amp;)${t}=1\\b(?!&amp;)|\\b${t}=1&amp;`),"");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: "&hellip;"
14
14
 
15
15
  info:
16
- no_items: "Kein %{item_name} gefunden"
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
@@ -7,7 +7,7 @@ ko:
7
7
 
8
8
  nav:
9
9
  prev: "&lsaquo;&nbsp;이전"
10
- next: "이후&nbsp;&rsaquo;"
10
+ next: "다음&nbsp;&rsaquo;"
11
11
  gap: "&hellip;"
12
12
 
13
13
  info:
@@ -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: "&lsaquo;&nbsp;Førre"
12
+ next: "Neste&nbsp;&rsaquo;"
13
+ gap: "&hellip;"
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>"
@@ -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: "&lsaquo;&nbsp;முந்தையது"
12
+ next: "அடுத்தது&nbsp;&rsaquo;"
13
+ gap: "&hellip;"
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
- # Defines a few generic methods to paginate an ORM collection out of the box,
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 # the whole module is private so no problem with including it in a controller
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
- [ pagy, pagy_get_items(collection, pagy) ]
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?(UseItemsExtra)
23
- vars[:count] ||= (c = collection.count(:all)).is_a?(Hash) ? c.size : c
24
- vars[:page] ||= params[ vars[:page_param] || VARS[:page_param] ]
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
- # include Pagy::Console in irb/rails console for a ready to use pagy environment
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
- VARS[:url] = 'http://www.example.com/subdir'
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