pagy 4.11.0 → 6.0.0

Sign up to get free protection for your applications and to get access to all the features.
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