pagy 7.0.11 → 43.5.1

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 (211) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/apps/calendar.ru +741 -0
  4. data/apps/demo.ru +513 -0
  5. data/apps/enable_rails_page_segment.rb +54 -0
  6. data/apps/index.rb +9 -0
  7. data/apps/keynav+root_key.ru +316 -0
  8. data/apps/keynav.ru +255 -0
  9. data/apps/keyset.ru +219 -0
  10. data/apps/keyset_sequel.ru +212 -0
  11. data/apps/rails.ru +216 -0
  12. data/apps/repro.ru +185 -0
  13. data/bin/pagy +5 -0
  14. data/config/pagy.rb +46 -0
  15. data/javascripts/ai_widget.js +90 -0
  16. data/javascripts/pagy.js +168 -0
  17. data/javascripts/pagy.min.js +2 -0
  18. data/javascripts/pagy.mjs +161 -0
  19. data/javascripts/wand.js +1172 -0
  20. data/lib/pagy/classes/calendar/calendar.rb +101 -0
  21. data/lib/pagy/{calendar → classes/calendar}/day.rb +9 -12
  22. data/lib/pagy/{calendar → classes/calendar}/month.rb +7 -11
  23. data/lib/pagy/{calendar → classes/calendar}/quarter.rb +12 -16
  24. data/lib/pagy/classes/calendar/unit.rb +93 -0
  25. data/lib/pagy/{calendar → classes/calendar}/week.rb +7 -11
  26. data/lib/pagy/{calendar → classes/calendar}/year.rb +9 -9
  27. data/lib/pagy/classes/exceptions.rb +26 -0
  28. data/lib/pagy/classes/keyset/adapters/active_record.rb +50 -0
  29. data/lib/pagy/classes/keyset/adapters/sequel.rb +62 -0
  30. data/lib/pagy/classes/keyset/keynav.rb +85 -0
  31. data/lib/pagy/classes/keyset/keyset.rb +150 -0
  32. data/lib/pagy/classes/offset/countish.rb +17 -0
  33. data/lib/pagy/classes/offset/countless.rb +63 -0
  34. data/lib/pagy/classes/offset/offset.rb +63 -0
  35. data/lib/pagy/classes/offset/search.rb +34 -0
  36. data/lib/pagy/classes/request.rb +48 -0
  37. data/lib/pagy/cli.rb +122 -0
  38. data/lib/pagy/console.rb +5 -20
  39. data/lib/pagy/deprecated.rb +84 -0
  40. data/lib/pagy/modules/abilities/configurable.rb +37 -0
  41. data/lib/pagy/modules/abilities/countable.rb +23 -0
  42. data/lib/pagy/modules/abilities/linkable.rb +72 -0
  43. data/lib/pagy/modules/abilities/rangeable.rb +14 -0
  44. data/lib/pagy/modules/abilities/shiftable.rb +12 -0
  45. data/lib/pagy/modules/b64.rb +35 -0
  46. data/lib/pagy/modules/console.rb +33 -0
  47. data/lib/pagy/modules/i18n/i18n.rb +72 -0
  48. data/lib/pagy/modules/i18n/p11n/arabic.rb +30 -0
  49. data/lib/pagy/modules/i18n/p11n/east_slavic.rb +27 -0
  50. data/lib/pagy/modules/i18n/p11n/one_other.rb +15 -0
  51. data/lib/pagy/modules/i18n/p11n/one_upto_two_other.rb +15 -0
  52. data/lib/pagy/modules/i18n/p11n/other.rb +13 -0
  53. data/lib/pagy/modules/i18n/p11n/polish.rb +27 -0
  54. data/lib/pagy/modules/i18n/p11n/west_slavic.rb +22 -0
  55. data/lib/pagy/modules/i18n/p11n.rb +16 -0
  56. data/lib/pagy/modules/searcher.rb +20 -0
  57. data/lib/pagy/next.rb +25 -0
  58. data/lib/pagy/tasks/sync.rb +20 -0
  59. data/lib/pagy/toolbox/helpers/anchor_tags.rb +21 -0
  60. data/lib/pagy/toolbox/helpers/bootstrap/input_nav_js.rb +28 -0
  61. data/lib/pagy/toolbox/helpers/bootstrap/previous_next_html.rb +19 -0
  62. data/lib/pagy/toolbox/helpers/bootstrap/series_nav.rb +32 -0
  63. data/lib/pagy/toolbox/helpers/bootstrap/series_nav_js.rb +24 -0
  64. data/lib/pagy/toolbox/helpers/bulma/input_nav_js.rb +25 -0
  65. data/lib/pagy/toolbox/helpers/bulma/previous_next_html.rb +20 -0
  66. data/lib/pagy/toolbox/helpers/bulma/series_nav.rb +31 -0
  67. data/lib/pagy/toolbox/helpers/bulma/series_nav_js.rb +23 -0
  68. data/lib/pagy/toolbox/helpers/data_hash.rb +29 -0
  69. data/lib/pagy/toolbox/helpers/headers_hash.rb +30 -0
  70. data/lib/pagy/toolbox/helpers/info_tag.rb +30 -0
  71. data/lib/pagy/toolbox/helpers/input_nav_js.rb +22 -0
  72. data/lib/pagy/toolbox/helpers/limit_tag_js.rb +25 -0
  73. data/lib/pagy/toolbox/helpers/loaders.rb +55 -0
  74. data/lib/pagy/toolbox/helpers/page_url.rb +16 -0
  75. data/lib/pagy/toolbox/helpers/series_nav.rb +30 -0
  76. data/lib/pagy/toolbox/helpers/series_nav_js.rb +20 -0
  77. data/lib/pagy/toolbox/helpers/support/a_lambda.rb +36 -0
  78. data/lib/pagy/toolbox/helpers/support/data_pagy_attribute.rb +19 -0
  79. data/lib/pagy/toolbox/helpers/support/nav_aria_label_attribute.rb +10 -0
  80. data/lib/pagy/toolbox/helpers/support/series.rb +37 -0
  81. data/lib/pagy/toolbox/helpers/support/wrap_input_nav_js.rb +19 -0
  82. data/lib/pagy/toolbox/helpers/support/wrap_series_nav.rb +17 -0
  83. data/lib/pagy/toolbox/helpers/support/wrap_series_nav_js.rb +42 -0
  84. data/lib/pagy/toolbox/helpers/urls_hash.rb +12 -0
  85. data/lib/pagy/toolbox/paginators/calendar.rb +34 -0
  86. data/lib/pagy/toolbox/paginators/countish.rb +38 -0
  87. data/lib/pagy/toolbox/paginators/countless.rb +22 -0
  88. data/lib/pagy/toolbox/paginators/elasticsearch_rails.rb +56 -0
  89. data/lib/pagy/toolbox/paginators/keynav_js.rb +26 -0
  90. data/lib/pagy/toolbox/paginators/keyset.rb +15 -0
  91. data/lib/pagy/toolbox/paginators/meilisearch.rb +34 -0
  92. data/lib/pagy/toolbox/paginators/method.rb +38 -0
  93. data/lib/pagy/toolbox/paginators/offset.rb +24 -0
  94. data/lib/pagy/toolbox/paginators/searchkick.rb +34 -0
  95. data/lib/pagy/toolbox/paginators/typesense_rails.rb +34 -0
  96. data/lib/pagy.rb +67 -131
  97. data/locales/ar.yml +32 -0
  98. data/locales/be.yml +28 -0
  99. data/locales/bg.yml +24 -0
  100. data/locales/bs.yml +28 -0
  101. data/locales/ca.yml +24 -0
  102. data/locales/ckb.yml +20 -0
  103. data/locales/cs.yml +26 -0
  104. data/locales/da.yml +24 -0
  105. data/locales/de.yml +24 -0
  106. data/locales/dz.yml +20 -0
  107. data/locales/en.yml +24 -0
  108. data/{lib/locales → locales}/es.yml +9 -6
  109. data/locales/fr.yml +24 -0
  110. data/locales/hr.yml +28 -0
  111. data/locales/id.yml +20 -0
  112. data/locales/it.yml +24 -0
  113. data/locales/ja.yml +20 -0
  114. data/locales/km.yml +20 -0
  115. data/locales/ko.yml +20 -0
  116. data/locales/nb.yml +24 -0
  117. data/locales/nl.yml +24 -0
  118. data/locales/nn.yml +24 -0
  119. data/locales/pl.yml +28 -0
  120. data/{lib/locales → locales}/pt-BR.yml +10 -7
  121. data/{lib/locales → locales}/pt.yml +10 -7
  122. data/locales/ru.yml +28 -0
  123. data/locales/sk.yml +26 -0
  124. data/locales/sr.yml +28 -0
  125. data/locales/sv-SE.yml +24 -0
  126. data/locales/sv.yml +24 -0
  127. data/locales/sw.yml +28 -0
  128. data/locales/ta.yml +24 -0
  129. data/locales/tr.yml +24 -0
  130. data/locales/uk.yml +28 -0
  131. data/locales/vi.yml +20 -0
  132. data/locales/zh-CN.yml +20 -0
  133. data/locales/zh-HK.yml +20 -0
  134. data/locales/zh-TW.yml +20 -0
  135. data/stylesheets/pagy-tailwind.css +68 -0
  136. data/stylesheets/pagy.css +83 -0
  137. metadata +185 -94
  138. data/lib/config/pagy.rb +0 -258
  139. data/lib/javascripts/pagy-dev.js +0 -112
  140. data/lib/javascripts/pagy-module.js +0 -111
  141. data/lib/javascripts/pagy.js +0 -1
  142. data/lib/locales/ar.yml +0 -30
  143. data/lib/locales/be.yml +0 -25
  144. data/lib/locales/bg.yml +0 -21
  145. data/lib/locales/bs.yml +0 -25
  146. data/lib/locales/ca.yml +0 -23
  147. data/lib/locales/ckb.yml +0 -18
  148. data/lib/locales/cs.yml +0 -23
  149. data/lib/locales/da.yml +0 -23
  150. data/lib/locales/de.yml +0 -21
  151. data/lib/locales/en.yml +0 -21
  152. data/lib/locales/fr.yml +0 -21
  153. data/lib/locales/hr.yml +0 -25
  154. data/lib/locales/id.yml +0 -19
  155. data/lib/locales/it.yml +0 -21
  156. data/lib/locales/ja.yml +0 -19
  157. data/lib/locales/km.yml +0 -19
  158. data/lib/locales/ko.yml +0 -19
  159. data/lib/locales/nb.yml +0 -21
  160. data/lib/locales/nl.yml +0 -21
  161. data/lib/locales/nn.yml +0 -21
  162. data/lib/locales/pl.yml +0 -25
  163. data/lib/locales/ru.yml +0 -27
  164. data/lib/locales/sr.yml +0 -25
  165. data/lib/locales/sv-SE.yml +0 -21
  166. data/lib/locales/sv.yml +0 -21
  167. data/lib/locales/sw.yml +0 -23
  168. data/lib/locales/ta.yml +0 -23
  169. data/lib/locales/tr.yml +0 -19
  170. data/lib/locales/uk.yml +0 -25
  171. data/lib/locales/vi.yml +0 -17
  172. data/lib/locales/zh-CN.yml +0 -19
  173. data/lib/locales/zh-HK.yml +0 -19
  174. data/lib/locales/zh-TW.yml +0 -19
  175. data/lib/pagy/backend.rb +0 -39
  176. data/lib/pagy/calendar/helper.rb +0 -65
  177. data/lib/pagy/calendar.rb +0 -126
  178. data/lib/pagy/countless.rb +0 -37
  179. data/lib/pagy/exceptions.rb +0 -25
  180. data/lib/pagy/extras/arel.rb +0 -36
  181. data/lib/pagy/extras/array.rb +0 -24
  182. data/lib/pagy/extras/bootstrap.rb +0 -108
  183. data/lib/pagy/extras/bulma.rb +0 -105
  184. data/lib/pagy/extras/calendar.rb +0 -53
  185. data/lib/pagy/extras/countless.rb +0 -37
  186. data/lib/pagy/extras/elasticsearch_rails.rb +0 -80
  187. data/lib/pagy/extras/foundation.rb +0 -105
  188. data/lib/pagy/extras/frontend_helpers.rb +0 -67
  189. data/lib/pagy/extras/gearbox.rb +0 -54
  190. data/lib/pagy/extras/headers.rb +0 -53
  191. data/lib/pagy/extras/i18n.rb +0 -26
  192. data/lib/pagy/extras/items.rb +0 -61
  193. data/lib/pagy/extras/jsonapi.rb +0 -79
  194. data/lib/pagy/extras/materialize.rb +0 -96
  195. data/lib/pagy/extras/meilisearch.rb +0 -65
  196. data/lib/pagy/extras/metadata.rb +0 -38
  197. data/lib/pagy/extras/navs.rb +0 -51
  198. data/lib/pagy/extras/overflow.rb +0 -80
  199. data/lib/pagy/extras/searchkick.rb +0 -67
  200. data/lib/pagy/extras/semantic.rb +0 -95
  201. data/lib/pagy/extras/standalone.rb +0 -60
  202. data/lib/pagy/extras/support.rb +0 -40
  203. data/lib/pagy/extras/trim.rb +0 -29
  204. data/lib/pagy/extras/uikit.rb +0 -97
  205. data/lib/pagy/frontend.rb +0 -114
  206. data/lib/pagy/i18n.rb +0 -165
  207. data/lib/pagy/url_helpers.rb +0 -27
  208. data/lib/stylesheets/pagy.css +0 -61
  209. data/lib/stylesheets/pagy.scss +0 -50
  210. data/lib/stylesheets/pagy.tailwind.scss +0 -24
  211. /data/{lib/javascripts/pagy-module.d.ts → javascripts/pagy.d.ts} +0 -0
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support'
4
+ require 'active_support/core_ext/time'
5
+ require 'active_support/core_ext/date_and_time/calculations'
6
+ require 'active_support/core_ext/numeric/time'
7
+ require 'active_support/core_ext/integer/time'
8
+
9
+ class Pagy
10
+ # Calendar class, subclass of Hash
11
+ class Calendar < Hash
12
+ path = Pathname.new(__dir__)
13
+ autoload :Unit, path.join('unit')
14
+ autoload :Day, path.join('day')
15
+ autoload :Week, path.join('week')
16
+ autoload :Month, path.join('month')
17
+ autoload :Quarter, path.join('quarter')
18
+ autoload :Year, path.join('year')
19
+
20
+ # List of units in desc order of duration. It can be used for custom units.
21
+ UNITS = %i[year quarter month week day] # rubocop:disable Style/MutableConstant
22
+
23
+ class << self
24
+ def localize_with_rails_i18n_gem(*locales)
25
+ Unit.prepend(Module.new { def localize(...) = ::I18n.localize(...) })
26
+ # :nocov:
27
+ raise RailsI18nLoadError, "Pagy: The gem 'rails-i18n' must be installed if you don't use Rails" \
28
+ unless (path = Gem.loaded_specs['rails-i18n']&.full_gem_path)
29
+
30
+ # :nocov:
31
+ path = Pathname.new(path)
32
+ ::I18n.load_path += locales.map { |locale| path.join("rails/locale/#{locale}.yml") }
33
+ end
34
+
35
+ private
36
+
37
+ # Return calendar, from, to
38
+ def init(...) = new.send(:init, ...)
39
+ end
40
+
41
+ # The current time of the smallest time unit shown
42
+ def showtime = self[@units.last].from
43
+
44
+ # The url for the calendar (shortest unit) page at time
45
+ def url_at(time, **)
46
+ page_keys = {}
47
+
48
+ @units.inject(nil) do |parent, unit|
49
+ unit_conf = @conf[unit]
50
+ unit_conf[:period] = parent&.send(:active_period) || @period
51
+ unit_conf[:page] = page = create(unit, **unit_conf).send(:page_at, time, **)
52
+
53
+ page_keys["#{unit}_#{@page_key}"] = page
54
+ unit_conf[:querify] = ->(params) { params.merge!(page_keys) }
55
+
56
+ create(unit, **unit_conf)
57
+ end.send(:compose_page_url, 1, **)
58
+ end
59
+
60
+ private
61
+
62
+ # Create the calendar
63
+ def init(conf, period, params)
64
+ @conf = conf
65
+ @units = Calendar::UNITS & conf.keys # get the units in time length desc order
66
+ @period = period
67
+ @params = params
68
+ @page_key = conf[:offset][:page_key] || DEFAULT[:page_key]
69
+
70
+ # Set all the :page_key options for later deletion
71
+ @units.each { |unit| conf[unit][:page_key] = "#{unit}_#{@page_key}" }
72
+
73
+ calendar = {}
74
+ unit_object = nil
75
+
76
+ @units.each_with_index do |unit, index|
77
+ params_to_delete = @units[(index + 1)..].map { conf[_1][:page_key] } + [@page_key]
78
+ unit_conf = conf[unit]
79
+ unit_conf[:querify] = ->(up) { up.except!(*params_to_delete.map(&:to_s)) }
80
+ unit_conf[:period] = unit_object&.send(:active_period) || @period
81
+ unit_conf[:page] = @params[unit_conf[:page_key]] # requested page
82
+ # Simplecov doesn't need to cover a fail block_given?
83
+ # :nocov:
84
+ unit_conf[:counts] = yield(unit, unit_conf[:period]) if block_given?
85
+ # :nocov:
86
+ calendar[unit] = unit_object = create(unit, **unit_conf)
87
+ end
88
+
89
+ [replace(calendar), unit_object.from, unit_object.to]
90
+ end
91
+
92
+ # Create an instance of a Unit subclass by using its name (internal use)
93
+ def create(unit, **)
94
+ raise InternalError, "unit must be in #{UNITS.inspect}; got #{unit}" unless UNITS.include?(unit)
95
+
96
+ unit_class = Pagy::Calendar.const_get(unit.to_s.capitalize)
97
+
98
+ unit_class.new(**, request: @conf[:request])
99
+ end
100
+ end
101
+ end
@@ -1,27 +1,24 @@
1
- # See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/docs/api/calendar
2
1
  # frozen_string_literal: true
3
2
 
4
- class Pagy # :nodoc:
5
- class Calendar # :nodoc:
6
- # Calendar day subclass
7
- class Day < Calendar
8
- DEFAULT = { size: 31, # rubocop:disable Style/MutableConstant
9
- order: :asc,
10
- format: '%d' }
3
+ class Pagy
4
+ class Calendar
5
+ class Day < Unit
6
+ DEFAULT = { slots: 31,
7
+ compact: true,
8
+ order: :asc,
9
+ format: '%d' }.freeze
11
10
 
12
11
  protected
13
12
 
14
- # Setup the calendar variables
15
- def setup_unit_vars
13
+ def assign_unit_variables
16
14
  super
17
15
  @initial = @starting.beginning_of_day
18
16
  @final = @ending.tomorrow.beginning_of_day
19
- @pages = @last = page_offset(@initial, @final)
17
+ @last = page_offset(@initial, @final)
20
18
  @from = starting_time_for(@page)
21
19
  @to = @from.tomorrow
22
20
  end
23
21
 
24
- # Starting time for the page
25
22
  def starting_time_for(page)
26
23
  @initial.days_since(time_offset_for(page))
27
24
  end
@@ -1,27 +1,24 @@
1
- # See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/docs/api/calendar
2
1
  # frozen_string_literal: true
3
2
 
4
3
  class Pagy # :nodoc:
5
4
  class Calendar # :nodoc:
6
- # Calendar month subclass
7
- class Month < Calendar
8
- DEFAULT = { size: 12, # rubocop:disable Style/MutableConstant
9
- order: :asc,
10
- format: '%b' }
5
+ class Month < Unit
6
+ DEFAULT = { slots: 12,
7
+ compact: true,
8
+ order: :asc,
9
+ format: '%b' }.freeze
11
10
 
12
11
  protected
13
12
 
14
- # Setup the calendar variables
15
- def setup_unit_vars
13
+ def assign_unit_variables
16
14
  super
17
15
  @initial = @starting.beginning_of_month
18
16
  @final = @ending.next_month.beginning_of_month
19
- @pages = @last = (months_in(@final) - months_in(@initial))
17
+ @last = (months_in(@final) - months_in(@initial))
20
18
  @from = starting_time_for(@page)
21
19
  @to = @from.next_month
22
20
  end
23
21
 
24
- # Starting time for the page
25
22
  def starting_time_for(page)
26
23
  @initial.months_since(time_offset_for(page))
27
24
  end
@@ -32,7 +29,6 @@ class Pagy # :nodoc:
32
29
 
33
30
  private
34
31
 
35
- # Number of months in time
36
32
  def months_in(time)
37
33
  (time.year * 12) + time.month
38
34
  end
@@ -1,34 +1,31 @@
1
- # See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/docs/api/calendar
2
1
  # frozen_string_literal: true
3
2
 
4
3
  class Pagy # :nodoc:
5
4
  class Calendar # :nodoc:
6
- # Calendar quarter subclass
7
- class Quarter < Calendar
8
- DEFAULT = { size: 4, # rubocop:disable Style/MutableConstant
9
- order: :asc,
10
- format: 'Q%q' } # '%q' token
5
+ class Quarter < Unit
6
+ DEFAULT = { slots: 4,
7
+ compact: true,
8
+ order: :asc,
9
+ format: 'Q%q' }.freeze # '%q' token
10
+
11
+ protected
11
12
 
12
13
  # The label for any page, with the substitution of the '%q' token
13
- def label_for(page, opts = {})
14
+ def page_label(page, **options)
14
15
  starting_time = starting_time_for(page.to_i) # page could be a string
15
- opts[:format] = (opts[:format] || @vars[:format]).gsub('%q') { (starting_time.month / 3.0).ceil }
16
- localize(starting_time, opts)
16
+ options[:format] = (options[:format] || @options[:format]).gsub('%q') { (starting_time.month / 3.0).ceil }
17
+ localize(starting_time, **options)
17
18
  end
18
19
 
19
- protected
20
-
21
- # Setup the calendar variables
22
- def setup_unit_vars
20
+ def assign_unit_variables
23
21
  super
24
22
  @initial = @starting.beginning_of_quarter
25
23
  @final = @ending.next_quarter.beginning_of_quarter
26
- @pages = @last = (months_in(@final) - months_in(@initial)) / 3
24
+ @last = (months_in(@final) - months_in(@initial)) / 3
27
25
  @from = starting_time_for(@page)
28
26
  @to = @from.next_quarter
29
27
  end
30
28
 
31
- # Starting time for the page
32
29
  def starting_time_for(page)
33
30
  @initial.months_since(time_offset_for(page) * 3)
34
31
  end
@@ -39,7 +36,6 @@ class Pagy # :nodoc:
39
36
 
40
37
  private
41
38
 
42
- # Number of months in time
43
39
  def months_in(time)
44
40
  (time.year * 12) + time.month
45
41
  end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../modules/abilities/shiftable'
4
+ require_relative '../../modules/abilities/rangeable'
5
+
6
+ class Pagy
7
+ class Calendar
8
+ # Base class for time units subclasses (Year, Quarter, Month, Week, Day).
9
+ #
10
+ # To define a "bimester" unit you should:
11
+ # - Define a `Pagy::Calendar::Bimester` class.
12
+ # - Add the `:bimester` unit symbol in the `Pagy::Calendar::UNITS`.
13
+ # - Ensure the desc duration order of the UNITS (i.e. insert it between `:quarter` and `:month`).
14
+ class Unit < Pagy
15
+ DEFAULT = { page: 1 }.freeze
16
+
17
+ include Rangeable
18
+ include Shiftable
19
+ include NumericHelpers
20
+
21
+ def initialize(**)
22
+ assign_options(**)
23
+ assign_and_check(page: 1)
24
+ assign_unit_variables
25
+ unless in_range? { @page <= @last }
26
+ assign_empty_page_variables
27
+ return
28
+ end
29
+
30
+ assign_previous_and_next
31
+ end
32
+
33
+ attr_reader :order, :from, :to, :previous, :last
34
+ alias pages last
35
+
36
+ protected
37
+
38
+ def calendar? = true
39
+
40
+ # Called by false in_range?
41
+ def assign_empty_page_variables
42
+ @in = @from = @to = 0 # options relative to the actual page
43
+ edge = @order == :asc ? @final : @initial # get the edge of the range (neat, but any time would do)
44
+ @from = @to = edge # set both to the edge time (a >=&&< query will get no records)
45
+ @previous = @last
46
+ end
47
+
48
+ # The page that includes time.
49
+ # In case of time out of range, the :fit_time option avoids the RangeError
50
+ # and returns the closest page to the passed time argument (first or last page).
51
+ def page_at(time, **options)
52
+ fit_time = time
53
+ fit_final = @final - 1
54
+ unless time.between?(@initial, fit_final)
55
+ raise RangeError.new(self, :time, "between #{@initial} and #{fit_final}", time) unless options[:fit_time]
56
+
57
+ fit_time = time < @final ? @initial : fit_final
58
+ end
59
+ offset = page_offset_at(fit_time) # offset starts from 0
60
+ @order == :asc ? offset + 1 : @last - offset
61
+ end
62
+
63
+ # Base class method for the setup of the unit variables (subclasses must implement it and call super).
64
+ def assign_unit_variables
65
+ @order = @options[:order]
66
+ @starting, @ending = @options[:period]
67
+ raise OptionError.new(self, :period, 'to be an Array of min and max TimeWithZone instances', @options[:period]) \
68
+ unless @starting.is_a?(ActiveSupport::TimeWithZone) \
69
+ && @ending.is_a?(ActiveSupport::TimeWithZone) && @starting <= @ending
70
+ end
71
+
72
+ # Apply the strftime format to the time.
73
+ # Localization other than :en, requires the rails-I18n gem.
74
+ def localize(time, **options)
75
+ # Impossible to "unprepend" the rails-i18n after it runs localize_with_rails_i18n_gem in test.
76
+ # :nocov:
77
+ time.strftime(options[:format])
78
+ # :nocov:
79
+ end
80
+
81
+ # The number of time units to offset from the @initial time, in order to get the ordered starting time for the page.
82
+ # Used in starting_time_for(page) where page starts from 1 (e.g. page to starting_time means subtracting 1).
83
+ def time_offset_for(page)
84
+ @order == :asc ? page - 1 : @last - page
85
+ end
86
+
87
+ # Period of the active page (used internally for nested units).
88
+ def active_period
89
+ [[@starting, @from].max, [@to - 1, @ending].min] # -1 sec: include only last unit day
90
+ end
91
+ end
92
+ end
93
+ end
@@ -1,26 +1,22 @@
1
- # See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/docs/api/calendar
2
1
  # frozen_string_literal: true
3
2
 
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' }
3
+ class Pagy
4
+ class Calendar
5
+ class Week < Unit
6
+ DEFAULT = { order: :asc,
7
+ format: '%Y-%W' }.freeze
10
8
 
11
9
  protected
12
10
 
13
- # Setup the calendar variables
14
- def setup_unit_vars
11
+ def assign_unit_variables
15
12
  super
16
13
  @initial = @starting.beginning_of_week
17
14
  @final = @ending.next_week.beginning_of_week
18
- @pages = @last = page_offset(@initial, @final)
15
+ @last = page_offset(@initial, @final)
19
16
  @from = starting_time_for(@page)
20
17
  @to = @from.next_week
21
18
  end
22
19
 
23
- # Starting time for the page
24
20
  def starting_time_for(page)
25
21
  @initial.weeks_since(time_offset_for(page))
26
22
  end
@@ -1,22 +1,22 @@
1
- # See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/docs/api/calendar
2
1
  # frozen_string_literal: true
3
2
 
4
3
  class Pagy # :nodoc:
5
4
  class Calendar # :nodoc:
6
- # Calendar year subclass
7
- class Year < Calendar
8
- DEFAULT = { size: 10, # rubocop:disable Style/MutableConstant
9
- order: :asc,
10
- format: '%Y' }
5
+ # Year unit subclass
6
+ class Year < Unit
7
+ DEFAULT = { slots: 10,
8
+ compact: true,
9
+ order: :asc,
10
+ format: '%Y' }.freeze
11
11
 
12
12
  protected
13
13
 
14
- # Setup the calendar variables
15
- def setup_unit_vars
14
+ # Set up the calendar variables
15
+ def assign_unit_variables
16
16
  super
17
17
  @initial = @starting.beginning_of_year
18
18
  @final = @ending.next_year.beginning_of_year
19
- @pages = @last = @final.year - @initial.year
19
+ @last = @final.year - @initial.year
20
20
  @from = starting_time_for(@page)
21
21
  @to = @from.next_year
22
22
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ # Specific subclass of ArgumentError
5
+ class OptionError < ArgumentError
6
+ attr_reader :pagy, :option, :value
7
+
8
+ # Prepare a useful feedback
9
+ def initialize(pagy, option, description, value)
10
+ @pagy = pagy
11
+ @option = option
12
+ @value = value
13
+
14
+ super("expected :#{@option} #{description}; got #{@value.inspect}")
15
+ end
16
+ end
17
+
18
+ # Specific subclass of OptionError
19
+ class RangeError < OptionError; end
20
+
21
+ # I18n localization error
22
+ class RailsI18nLoadError < LoadError; end
23
+
24
+ # Generic internal error
25
+ class InternalError < StandardError; end
26
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ class Keyset
5
+ module Adapters
6
+ # Keyset adapter for ActiveRecord
7
+ module ActiveRecord
8
+ def extract_keyset
9
+ @set.order_values.each_with_object({}) do |node, keyset|
10
+ keyset[node.value.name.to_sym] ||= node.direction
11
+ end
12
+ end
13
+
14
+ def keyset_attributes_from(record)
15
+ record.slice(*@keyset.keys)
16
+ end
17
+
18
+ def quoted_identifiers(table)
19
+ connection = @set.connection
20
+ @keyset.to_h { |column| [column, "#{connection.quote_table_name(table)}.#{connection.quote_column_name(column)}"] }
21
+ end
22
+
23
+ def typecast(attributes)
24
+ model = @set.model
25
+ {}.tap do |result|
26
+ @keyset.each_key do |k|
27
+ result[k] = model.type_for_attribute(k).cast(attributes[k])
28
+ end
29
+ end
30
+ end
31
+
32
+ def ensure_select
33
+ return if @set.select_values.empty?
34
+
35
+ @set = @set.select(*@keyset.keys)
36
+ end
37
+
38
+ def apply_where(predicate, arguments)
39
+ @set = @set.where(predicate, **arguments)
40
+ end
41
+
42
+ def self.included(including)
43
+ instance_methods(false).each do |method_name|
44
+ including.send(:protected, method_name)
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ class Keyset
5
+ module Adapters
6
+ # Keyset adapter for Sequel ORM
7
+ module Sequel
8
+ def extract_keyset
9
+ return {} unless @set.opts[:order]
10
+
11
+ @set.opts[:order].each_with_object({}) do |item, keyset|
12
+ case item
13
+ when Symbol
14
+ keyset[item] ||= :asc
15
+ when ::Sequel::SQL::OrderedExpression
16
+ keyset[item.expression] ||= (item.descending ? :desc : :asc)
17
+ else
18
+ raise TypeError, "#{item.class.inspect} is not a supported Sequel::SQL::OrderedExpression"
19
+ end
20
+ end
21
+ end
22
+
23
+ def keyset_attributes_from(record)
24
+ record.to_hash.slice(*@keyset.keys)
25
+ end
26
+
27
+ def quoted_identifiers(table)
28
+ db = @set.db
29
+ @keyset.to_h { |column| [column, "#{db.quote_identifier(table)}.#{db.quote_identifier(column)}"] }
30
+ end
31
+
32
+ def typecast(attributes)
33
+ model = @set.model
34
+ db = @set.db
35
+ {}.tap do |result|
36
+ @keyset.each_key do |k|
37
+ type = model.db_schema[k].fetch(:type)
38
+ result[k] = db.typecast_value(type, attributes[k])
39
+ end
40
+ end
41
+ end
42
+
43
+ def ensure_select
44
+ return if @set.opts[:select].nil?
45
+
46
+ selected = @set.opts[:select]
47
+ @set = @set.select_append(*@keyset.keys.reject { |c| selected.include?(c) })
48
+ end
49
+
50
+ def apply_where(predicate, arguments)
51
+ @set = @set.where(::Sequel.lit(predicate, **arguments))
52
+ end
53
+
54
+ def self.included(including)
55
+ instance_methods(false).each do |method_name|
56
+ including.send(:protected, method_name)
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ class Keyset
5
+ # Keyset pagination with broad UI support
6
+ class Keynav < Keyset
7
+ # Avoid conflicts between filter arguments in composite SQL fragments
8
+ PRIOR_PREFIX = 'prior_'
9
+ PAGE_PREFIX = 'page_'
10
+
11
+ # Define empty subclasses to allow specific typing without triggering autoload
12
+ class ActiveRecord < self; end
13
+ class Sequel < self; end
14
+
15
+ include NumericHelpers
16
+
17
+ # Finalize the instance variables needed for the UI
18
+ def initialize(set, **)
19
+ super
20
+
21
+ @previous = @page - 1 unless @page == 1
22
+ @in = @records.size
23
+ end
24
+
25
+ attr_reader :update, :previous, :last
26
+ alias pages last
27
+
28
+ # Prepare the @update for the client when it's a new page, and return the next page number
29
+ def next
30
+ records
31
+ @count = 0 if !@more && @page == 1 # empty records (trigger the right info message for known 0 count)
32
+ return unless @more
33
+
34
+ @next ||= begin
35
+ unless @page_cutoff
36
+ @page_cutoff = extract_cutoff
37
+ @last += 1 # reflect the added cutoff
38
+ @update.push(@last, [@page, 1, @page_cutoff]) # last + splice arguments for the client cutoffs
39
+ end
40
+ @page + 1
41
+ end
42
+ end
43
+
44
+ protected
45
+
46
+ def keynav? = true
47
+
48
+ # Process the page array
49
+ def assign_page
50
+ if @options[:page]
51
+ storage_key, @page, @last, @prior_cutoff, @page_cutoff = @options[:page]
52
+ else
53
+ @page = @last = 1
54
+ end
55
+
56
+ @update = [storage_key, @options[:root_key], @options[:page_key]]
57
+ end
58
+
59
+ # Use a compound predicate to fetch the records
60
+ def fetch_records
61
+ return super unless @page_cutoff # last page
62
+
63
+ # Compound predicate for visited pages
64
+ predicate = +''
65
+ arguments = {}
66
+
67
+ if @prior_cutoff # not the first page
68
+ # Include the records after @prior_cutoff
69
+ predicate << "(#{compose_predicate(PRIOR_PREFIX)}) AND "
70
+ arguments.merge!(arguments_from(@prior_cutoff, PRIOR_PREFIX))
71
+ end
72
+
73
+ # Exclude the records after @page_cutoff
74
+ predicate << "NOT (#{compose_predicate(PAGE_PREFIX)})"
75
+ arguments.merge!(arguments_from(@page_cutoff, PAGE_PREFIX))
76
+ apply_where(predicate, arguments)
77
+
78
+ @more = true # not the last page
79
+ @set.limit(nil).to_a # replaced by the compound predicate
80
+ end
81
+
82
+ prepend Deprecated::Keynav if defined?(Deprecated::Keynav)
83
+ end
84
+ end
85
+ end