pagy 9.3.5 → 43.0.0.rc2

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 (168) hide show
  1. checksums.yaml +4 -4
  2. data/apps/calendar.ru +547 -551
  3. data/apps/demo.ru +221 -178
  4. data/apps/index.rb +3 -1
  5. data/apps/keynav.ru +258 -0
  6. data/apps/{keyset_ar.ru → keyset.ru} +27 -32
  7. data/apps/{keyset_s.ru → keyset_sequel.ru} +24 -29
  8. data/apps/rails.ru +51 -48
  9. data/apps/repro.ru +46 -43
  10. data/bin/pagy +20 -21
  11. data/config/pagy.rb +35 -207
  12. data/javascripts/ai_widget.js +76 -0
  13. data/javascripts/pagy.js +151 -0
  14. data/javascripts/pagy.js.map +10 -0
  15. data/javascripts/pagy.min.js +1 -4
  16. data/javascripts/pagy.mjs +111 -63
  17. data/javascripts/wand.js +1166 -0
  18. data/lib/pagy/classes/calendar/calendar.rb +98 -0
  19. data/lib/pagy/{calendar → classes/calendar}/day.rb +7 -11
  20. data/lib/pagy/{calendar → classes/calendar}/month.rb +5 -10
  21. data/lib/pagy/{calendar → classes/calendar}/quarter.rb +10 -15
  22. data/lib/pagy/classes/calendar/unit.rb +92 -0
  23. data/lib/pagy/{calendar → classes/calendar}/week.rb +5 -9
  24. data/lib/pagy/{calendar → classes/calendar}/year.rb +5 -6
  25. data/lib/pagy/classes/exceptions.rb +33 -0
  26. data/lib/pagy/classes/keyset/active_record.rb +11 -0
  27. data/lib/pagy/classes/keyset/adapters/active_record.rb +50 -0
  28. data/lib/pagy/classes/keyset/adapters/sequel.rb +63 -0
  29. data/lib/pagy/classes/keyset/keynav/active_record.rb +13 -0
  30. data/lib/pagy/classes/keyset/keynav/sequel.rb +13 -0
  31. data/lib/pagy/classes/keyset/keynav.rb +77 -0
  32. data/lib/pagy/classes/keyset/keyset.rb +126 -0
  33. data/lib/pagy/classes/keyset/sequel.rb +11 -0
  34. data/lib/pagy/classes/offset/countless.rb +56 -0
  35. data/lib/pagy/classes/offset/offset.rb +54 -0
  36. data/lib/pagy/classes/offset/search.rb +38 -0
  37. data/lib/pagy/classes/request.rb +36 -0
  38. data/lib/pagy/modules/abilities/configurable.rb +36 -0
  39. data/lib/pagy/modules/abilities/linkable.rb +59 -0
  40. data/lib/pagy/modules/abilities/rangeable.rb +15 -0
  41. data/lib/pagy/modules/abilities/shiftable.rb +12 -0
  42. data/lib/pagy/{b64.rb → modules/b64.rb} +3 -7
  43. data/lib/pagy/modules/console.rb +38 -0
  44. data/lib/pagy/modules/i18n/i18n.rb +48 -0
  45. data/lib/pagy/modules/i18n/p11n/arabic.rb +29 -0
  46. data/lib/pagy/modules/i18n/p11n/east_slavic.rb +26 -0
  47. data/lib/pagy/modules/i18n/p11n/one_other.rb +15 -0
  48. data/lib/pagy/modules/i18n/p11n/one_upto_two_other.rb +15 -0
  49. data/lib/pagy/modules/i18n/p11n/other.rb +13 -0
  50. data/lib/pagy/modules/i18n/p11n/polish.rb +26 -0
  51. data/lib/pagy/modules/i18n/p11n/west_slavic.rb +22 -0
  52. data/lib/pagy/modules/i18n/p11n.rb +16 -0
  53. data/lib/pagy/modules/searcher.rb +20 -0
  54. data/lib/pagy/toolbox/helpers/anchor_tags.rb +25 -0
  55. data/lib/pagy/toolbox/helpers/bootstrap/input_nav_js.rb +24 -0
  56. data/lib/pagy/toolbox/helpers/bootstrap/previous_next_html.rb +18 -0
  57. data/lib/pagy/toolbox/helpers/bootstrap/series_nav.rb +29 -0
  58. data/lib/pagy/toolbox/helpers/bootstrap/series_nav_js.rb +21 -0
  59. data/lib/pagy/toolbox/helpers/bulma/input_nav_js.rb +21 -0
  60. data/lib/pagy/toolbox/helpers/bulma/previous_next_html.rb +19 -0
  61. data/lib/pagy/toolbox/helpers/bulma/series_nav.rb +28 -0
  62. data/lib/pagy/toolbox/helpers/bulma/series_nav_js.rb +20 -0
  63. data/lib/pagy/toolbox/helpers/data_hash.rb +26 -0
  64. data/lib/pagy/toolbox/helpers/headers_hash.rb +20 -0
  65. data/lib/pagy/toolbox/helpers/info_tag.rb +26 -0
  66. data/lib/pagy/toolbox/helpers/input_nav_js.rb +19 -0
  67. data/lib/pagy/toolbox/helpers/limit_tag_js.rb +23 -0
  68. data/lib/pagy/toolbox/helpers/loader.rb +33 -0
  69. data/lib/pagy/toolbox/helpers/page_url.rb +23 -0
  70. data/lib/pagy/toolbox/helpers/series_nav.rb +29 -0
  71. data/lib/pagy/toolbox/helpers/series_nav_js.rb +19 -0
  72. data/lib/pagy/toolbox/helpers/support/a_lambda.rb +34 -0
  73. data/lib/pagy/toolbox/helpers/support/data_pagy_attribute.rb +15 -0
  74. data/lib/pagy/toolbox/helpers/support/nav_aria_label_attribute.rb +12 -0
  75. data/lib/pagy/toolbox/helpers/support/series.rb +38 -0
  76. data/lib/pagy/toolbox/helpers/support/wrap_input_nav_js.rb +20 -0
  77. data/lib/pagy/toolbox/helpers/support/wrap_series_nav.rb +17 -0
  78. data/lib/pagy/toolbox/helpers/support/wrap_series_nav_js.rb +36 -0
  79. data/lib/pagy/toolbox/helpers/urls_hash.rb +13 -0
  80. data/lib/pagy/toolbox/paginators/calendar.rb +30 -0
  81. data/lib/pagy/toolbox/paginators/countless.rb +25 -0
  82. data/lib/pagy/toolbox/paginators/elasticsearch_rails.rb +32 -0
  83. data/lib/pagy/toolbox/paginators/keynav_js.rb +29 -0
  84. data/lib/pagy/toolbox/paginators/keyset.rb +18 -0
  85. data/lib/pagy/toolbox/paginators/meilisearch.rb +32 -0
  86. data/lib/pagy/toolbox/paginators/method.rb +26 -0
  87. data/lib/pagy/toolbox/paginators/offset.rb +33 -0
  88. data/lib/pagy/toolbox/paginators/searchkick.rb +32 -0
  89. data/lib/pagy.rb +59 -97
  90. data/locales/ar.yml +9 -6
  91. data/locales/be.yml +9 -6
  92. data/locales/bg.yml +9 -6
  93. data/locales/bs.yml +9 -6
  94. data/locales/ca.yml +9 -6
  95. data/locales/ckb.yml +8 -6
  96. data/locales/cs.yml +9 -6
  97. data/locales/da.yml +9 -6
  98. data/locales/de.yml +9 -6
  99. data/locales/dz.yml +9 -6
  100. data/locales/en.yml +9 -6
  101. data/locales/es.yml +9 -6
  102. data/locales/fr.yml +9 -6
  103. data/locales/hr.yml +9 -6
  104. data/locales/id.yml +9 -6
  105. data/locales/it.yml +9 -6
  106. data/locales/ja.yml +9 -6
  107. data/locales/km.yml +9 -6
  108. data/locales/ko.yml +9 -6
  109. data/locales/nb.yml +9 -6
  110. data/locales/nl.yml +9 -6
  111. data/locales/nn.yml +9 -6
  112. data/locales/pl.yml +9 -6
  113. data/locales/pt-BR.yml +10 -7
  114. data/locales/pt.yml +10 -7
  115. data/locales/ru.yml +9 -6
  116. data/locales/sk.yml +26 -0
  117. data/locales/sr.yml +9 -6
  118. data/locales/sv-SE.yml +9 -6
  119. data/locales/sv.yml +9 -6
  120. data/locales/sw.yml +12 -9
  121. data/locales/ta.yml +12 -13
  122. data/locales/tr.yml +9 -6
  123. data/locales/uk.yml +9 -6
  124. data/locales/vi.yml +9 -6
  125. data/locales/zh-CN.yml +9 -6
  126. data/locales/zh-HK.yml +9 -6
  127. data/locales/zh-TW.yml +9 -6
  128. data/stylesheets/pagy-tailwind.css +64 -0
  129. data/stylesheets/pagy.css +63 -27
  130. metadata +113 -51
  131. data/javascripts/pagy.min.js.map +0 -10
  132. data/lib/pagy/backend.rb +0 -44
  133. data/lib/pagy/calendar/unit.rb +0 -103
  134. data/lib/pagy/calendar.rb +0 -84
  135. data/lib/pagy/console.rb +0 -23
  136. data/lib/pagy/countless.rb +0 -38
  137. data/lib/pagy/exceptions.rb +0 -25
  138. data/lib/pagy/extras/arel.rb +0 -28
  139. data/lib/pagy/extras/array.rb +0 -19
  140. data/lib/pagy/extras/bootstrap.rb +0 -97
  141. data/lib/pagy/extras/bulma.rb +0 -93
  142. data/lib/pagy/extras/calendar.rb +0 -79
  143. data/lib/pagy/extras/countless.rb +0 -32
  144. data/lib/pagy/extras/elasticsearch_rails.rb +0 -71
  145. data/lib/pagy/extras/gearbox.rb +0 -55
  146. data/lib/pagy/extras/headers.rb +0 -54
  147. data/lib/pagy/extras/i18n.rb +0 -26
  148. data/lib/pagy/extras/js_tools.rb +0 -70
  149. data/lib/pagy/extras/jsonapi.rb +0 -88
  150. data/lib/pagy/extras/keyset.rb +0 -30
  151. data/lib/pagy/extras/limit.rb +0 -63
  152. data/lib/pagy/extras/meilisearch.rb +0 -57
  153. data/lib/pagy/extras/metadata.rb +0 -42
  154. data/lib/pagy/extras/overflow.rb +0 -81
  155. data/lib/pagy/extras/pagy.rb +0 -82
  156. data/lib/pagy/extras/searchkick.rb +0 -59
  157. data/lib/pagy/extras/size.rb +0 -40
  158. data/lib/pagy/extras/standalone.rb +0 -60
  159. data/lib/pagy/extras/trim.rb +0 -29
  160. data/lib/pagy/frontend.rb +0 -99
  161. data/lib/pagy/i18n.rb +0 -166
  162. data/lib/pagy/keyset/active_record.rb +0 -44
  163. data/lib/pagy/keyset/sequel.rb +0 -57
  164. data/lib/pagy/keyset.rb +0 -118
  165. data/lib/pagy/shared_methods.rb +0 -26
  166. data/lib/pagy/url_helpers.rb +0 -26
  167. data/stylesheets/pagy.scss +0 -48
  168. data/stylesheets/pagy.tailwind.css +0 -21
@@ -0,0 +1,98 @@
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
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
+ # :nocov:
25
+ # Localize with rails-i18n in any env
26
+ def localize_with_rails_i18n_gem(*locales)
27
+ Unit.prepend(Module.new { def localize(...) = ::I18n.localize(...) })
28
+ raise RailsI18nLoadError, "Pagy: The gem 'rails-i18n' must be installed if you don't use Rails" \
29
+ unless (path = Gem.loaded_specs['rails-i18n']&.full_gem_path)
30
+
31
+ path = Pathname.new(path)
32
+ ::I18n.load_path += locales.map { |locale| path.join("rails/locale/#{locale}.yml") }
33
+ end
34
+ # :nocov:
35
+
36
+ private
37
+
38
+ # Return calendar, from, to
39
+ def init(...)
40
+ new.send(:init, ...)
41
+ end
42
+ end
43
+
44
+ # Return the current time of the smallest time unit shown
45
+ def showtime
46
+ self[@units.last].from
47
+ end
48
+
49
+ # Return the url for the calendar (shortest unit) page at time
50
+ def url_at(time, **)
51
+ conf = Marshal.load(Marshal.dump(@conf))
52
+ page_keys = {}
53
+ @units.inject(nil) do |object, unit|
54
+ conf[unit][:period] = object&.send(:active_period) || @period
55
+ conf[unit][:page] = page_keys["#{unit}_#{@page_key}"] \
56
+ = create(unit, **conf[unit]).send(:page_at, time, **)
57
+ conf[unit][:querify] = ->(query) { query.merge!(page_keys) }
58
+ create(unit, **conf[unit])
59
+ end.send(:compose_page_url, 1, **)
60
+ end
61
+
62
+ private
63
+
64
+ # Create the calendar
65
+ def init(conf, period, query)
66
+ @conf = Marshal.load(Marshal.dump(conf)) # store a copy
67
+ @units = Calendar::UNITS & @conf.keys # get the units in time length desc order
68
+ @period = period
69
+ @query = query
70
+ @page_key = conf[:offset][:page_key] || DEFAULT[:page_key]
71
+ # set all the :page_key options for later deletion
72
+ @units.each { |unit| conf[unit][:page_key] = "#{unit}_#{@page_key}" }
73
+ calendar = {}
74
+ object = nil
75
+ @units.each_with_index do |unit, index|
76
+ params_to_delete = @units[(index + 1), @units.length].map { |sub| conf[sub][:page_key] } + [@page_key]
77
+ conf[unit][:querify] = ->(up) { up.except!(*params_to_delete.map(&:to_s)) }
78
+ conf[unit][:period] = object&.send(:active_period) || @period
79
+ conf[unit][:page] = @query["#{unit}_#{@page_key}"] # requested page
80
+ # :nocov:
81
+ # simplecov doesn't need to fail block_given?
82
+ conf[unit][:counts] = yield(unit, conf[unit][:period]) if block_given?
83
+ # :nocov:
84
+ calendar[unit] = object = create(unit, **conf[unit])
85
+ end
86
+ [replace(calendar), object.from, object.to]
87
+ end
88
+
89
+ # Create a unit subclass instance by using the unit name (internal use)
90
+ def create(unit, **)
91
+ raise InternalError, "unit must be in #{UNITS.inspect}; got #{unit}" unless UNITS.include?(unit)
92
+
93
+ name = +unit.to_s
94
+ name[0] = name[0].capitalize
95
+ Pagy::Calendar.const_get(name).new(**, request: @conf[:request])
96
+ end
97
+ end
98
+ end
@@ -1,19 +1,16 @@
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
- # Day unit subclass
3
+ class Pagy
4
+ class Calendar
7
5
  class Day < Unit
8
- DEFAULT = { size: 31, # rubocop:disable Style/MutableConstant
9
- ends: false,
10
- order: :asc,
11
- format: '%d' }
6
+ DEFAULT = { slots: 31,
7
+ compact: true,
8
+ order: :asc,
9
+ format: '%d' }.freeze
12
10
 
13
11
  protected
14
12
 
15
- # Set up the calendar variables
16
- def assign_unit_vars
13
+ def assign_unit_variables
17
14
  super
18
15
  @initial = @starting.beginning_of_day
19
16
  @final = @ending.tomorrow.beginning_of_day
@@ -22,7 +19,6 @@ class Pagy # :nodoc:
22
19
  @to = @from.tomorrow
23
20
  end
24
21
 
25
- # Starting time for the page
26
22
  def starting_time_for(page)
27
23
  @initial.days_since(time_offset_for(page))
28
24
  end
@@ -1,19 +1,16 @@
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
- # Month unit subclass
7
5
  class Month < Unit
8
- DEFAULT = { size: 12, # rubocop:disable Style/MutableConstant
9
- ends: false,
10
- order: :asc,
11
- format: '%b' }
6
+ DEFAULT = { slots: 12,
7
+ compact: true,
8
+ order: :asc,
9
+ format: '%b' }.freeze
12
10
 
13
11
  protected
14
12
 
15
- # Set up the calendar variables
16
- def assign_unit_vars
13
+ def assign_unit_variables
17
14
  super
18
15
  @initial = @starting.beginning_of_month
19
16
  @final = @ending.next_month.beginning_of_month
@@ -22,7 +19,6 @@ class Pagy # :nodoc:
22
19
  @to = @from.next_month
23
20
  end
24
21
 
25
- # Starting time for the page
26
22
  def starting_time_for(page)
27
23
  @initial.months_since(time_offset_for(page))
28
24
  end
@@ -33,7 +29,6 @@ class Pagy # :nodoc:
33
29
 
34
30
  private
35
31
 
36
- # Number of months in time
37
32
  def months_in(time)
38
33
  (time.year * 12) + time.month
39
34
  end
@@ -1,26 +1,23 @@
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
- # Quarter unit subclass
7
5
  class Quarter < Unit
8
- DEFAULT = { size: 4, # rubocop:disable Style/MutableConstant
9
- ends: false,
10
- order: :asc,
11
- format: 'Q%q' } # '%q' token
6
+ DEFAULT = { slots: 4,
7
+ compact: true,
8
+ order: :asc,
9
+ format: 'Q%q' }.freeze # '%q' token
10
+
11
+ protected
12
12
 
13
13
  # The label for any page, with the substitution of the '%q' token
14
- def label_for(page, opts = {})
14
+ def page_label(page, **options)
15
15
  starting_time = starting_time_for(page.to_i) # page could be a string
16
- opts[:format] = (opts[:format] || @vars[:format]).gsub('%q') { (starting_time.month / 3.0).ceil }
17
- localize(starting_time, opts)
16
+ options[:format] = (options[:format] || @options[:format]).gsub('%q') { (starting_time.month / 3.0).ceil }
17
+ localize(starting_time, **options)
18
18
  end
19
19
 
20
- protected
21
-
22
- # Set up the calendar variables
23
- def assign_unit_vars
20
+ def assign_unit_variables
24
21
  super
25
22
  @initial = @starting.beginning_of_quarter
26
23
  @final = @ending.next_quarter.beginning_of_quarter
@@ -29,7 +26,6 @@ class Pagy # :nodoc:
29
26
  @to = @from.next_quarter
30
27
  end
31
28
 
32
- # Starting time for the page
33
29
  def starting_time_for(page)
34
30
  @initial.months_since(time_offset_for(page) * 3)
35
31
  end
@@ -40,7 +36,6 @@ class Pagy # :nodoc:
40
36
 
41
37
  private
42
38
 
43
- # Number of months in time
44
39
  def months_in(time)
45
40
  (time.year * 12) + time.month
46
41
  end
@@ -0,0 +1,92 @@
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 durtion 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
+
20
+ def initialize(**) # rubocop:disable Lint/MissingSuper
21
+ assign_options(**)
22
+ assign_and_check(page: 1)
23
+ assign_unit_variables
24
+ return unless in_range? { @page <= @last }
25
+
26
+ assign_previous_and_next
27
+ end
28
+
29
+ attr_reader :order, :from, :to, :previous, :last
30
+
31
+ protected
32
+
33
+ def calendar? = true
34
+
35
+ # Called by false in_range?
36
+ def assign_empty_page_variables
37
+ @in = @from = @to = 0 # options relative to the actual page
38
+ edge = @order == :asc ? @final : @initial # get the edge of the range (neat, but any time would do)
39
+ @from = @to = edge # set both to the edge time (a >=&&< query will get no records)
40
+ @previous = @last
41
+ end
42
+
43
+ # The page that includes time
44
+ # In case of time out of range, the :fit_time option avoids the RangeError
45
+ # and returns the closest page to the passed time argument (first or last page)
46
+ def page_at(time, **options)
47
+ fit_time = time
48
+ fit_final = @final - 1
49
+ unless time.between?(@initial, fit_final)
50
+ raise RangeError.new(self, :time, "between #{@initial} and #{fit_final}", time) unless options[:fit_time]
51
+
52
+ if time < @final
53
+ fit_time = @initial
54
+ ordinal = 'first'
55
+ else
56
+ fit_time = fit_final
57
+ ordinal = 'last'
58
+ end
59
+ warn "Pagy::Calendar#page_at: Rescued #{time} out of range by returning the #{ordinal} page."
60
+ end
61
+ offset = page_offset_at(fit_time) # offset starts from 0
62
+ @order == :asc ? offset + 1 : @last - offset
63
+ end
64
+
65
+ # Base class method for the setup of the unit variables (subclasses must implement it and call super)
66
+ def assign_unit_variables
67
+ @order = @options[:order]
68
+ @starting, @ending = @options[:period]
69
+ raise OptionError.new(self, :period, 'to be a an Array of min and max TimeWithZone instances', @options[:period]) \
70
+ unless @starting.is_a?(ActiveSupport::TimeWithZone) \
71
+ && @ending.is_a?(ActiveSupport::TimeWithZone) && @starting <= @ending
72
+ end
73
+
74
+ # Apply the strftime format to the time.
75
+ # Localization other than :en, require the rails-I18n gem.
76
+ def localize(time, **options)
77
+ time.strftime(options[:format])
78
+ end
79
+
80
+ # The number of time units to offset from the @initial time, in order to get the ordered starting time for the page.
81
+ # Used in starting_time_for(page) where page starts from 1 (e.g. page to starting_time means subtracting 1)
82
+ def time_offset_for(page)
83
+ @order == :asc ? page - 1 : @last - page
84
+ end
85
+
86
+ # Period of the active page (used internally for nested units)
87
+ def active_period
88
+ [[@starting, @from].max, [@to - 1, @ending].min] # -1 sec: include only last unit day
89
+ end
90
+ end
91
+ end
92
+ end
@@ -1,17 +1,14 @@
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
- # Week unit subclass
3
+ class Pagy
4
+ class Calendar
7
5
  class Week < Unit
8
- DEFAULT = { order: :asc, # rubocop:disable Style/MutableConstant
9
- format: '%Y-%W' }
6
+ DEFAULT = { order: :asc,
7
+ format: '%Y-%W' }.freeze
10
8
 
11
9
  protected
12
10
 
13
- # Set up the calendar variables
14
- def assign_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
@@ -20,7 +17,6 @@ class Pagy # :nodoc:
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,19 +1,18 @@
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
5
  # Year unit subclass
7
6
  class Year < Unit
8
- DEFAULT = { size: 10, # rubocop:disable Style/MutableConstant
9
- ends: false,
10
- order: :asc,
11
- format: '%Y' }
7
+ DEFAULT = { slots: 10,
8
+ compact: true,
9
+ order: :asc,
10
+ format: '%Y' }.freeze
12
11
 
13
12
  protected
14
13
 
15
14
  # Set up the calendar variables
16
- def assign_unit_vars
15
+ def assign_unit_variables
17
16
  super
18
17
  @initial = @starting.beginning_of_year
19
18
  @final = @ending.next_year.beginning_of_year
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ # Generic option error
5
+ class OptionError < ArgumentError
6
+ attr_reader :pagy, :option, :value
7
+
8
+ # Set the options and prepare the message
9
+ def initialize(pagy, option, description, value)
10
+ @pagy = pagy
11
+ @option = option
12
+ @value = value
13
+ super("expected :#{@option} #{description}; got #{@value.inspect}")
14
+ end
15
+ end
16
+
17
+ # Specific range error
18
+ class RangeError < OptionError; end
19
+
20
+ # I18n localization error
21
+ class RailsI18nLoadError < LoadError; end
22
+
23
+ # Generic internal error
24
+ class InternalError < StandardError; end
25
+
26
+ # JsonApi :page param error
27
+ class JsonapiReservedParamError < StandardError
28
+ # Inform about the actual value
29
+ def initialize(value)
30
+ super("expected reserved :page param to be nil or Hash-like; got #{value.inspect}")
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'adapters/active_record'
4
+
5
+ class Pagy
6
+ class Keyset
7
+ class ActiveRecord < Keyset
8
+ include Adapters::ActiveRecord
9
+ end
10
+ end
11
+ 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
+ # Extract the keyset from the set
9
+ def extract_keyset
10
+ @set.order_values.each_with_object({}) do |node, keyset|
11
+ keyset[node.value.name.to_sym] = node.direction
12
+ end
13
+ end
14
+
15
+ # Get the keyset attributes from a record
16
+ def keyset_attributes_from(record) = record.slice(*@keyset.keys)
17
+
18
+ # Get the hash of quoted keyset identifiers
19
+ def quoted_identifiers(table)
20
+ connection = @set.connection
21
+ @keyset.to_h { |column| [column, "#{connection.quote_table_name(table)}.#{connection.quote_column_name(column)}"] }
22
+ end
23
+
24
+ # Typecast the attributes
25
+ def typecast(attributes)
26
+ @set.model.new(attributes).slice(attributes.keys)
27
+ .to_hash.transform_keys(&:to_sym)
28
+ end
29
+
30
+ # Append the missing keyset keys, if the set is restricted by select
31
+ def ensure_select
32
+ return if @set.select_values.empty?
33
+
34
+ @set = @set.select(*@keyset.keys)
35
+ end
36
+
37
+ # Apply the where predicate to the set
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,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ class Keyset
5
+ module Adapters
6
+ # Keyset adapter for sequel
7
+ module Sequel
8
+ # Extract the keyset from the set
9
+ def extract_keyset
10
+ return {} unless @set.opts[:order]
11
+
12
+ @set.opts[:order].each_with_object({}) do |item, keyset|
13
+ case item
14
+ when Symbol
15
+ keyset[item] = :asc
16
+ when ::Sequel::SQL::OrderedExpression
17
+ keyset[item.expression] = item.descending ? :desc : :asc
18
+ else
19
+ raise TypeError, "#{item.class.inspect} is not a supported Sequel::SQL::OrderedExpression"
20
+ end
21
+ end
22
+ end
23
+
24
+ # Get the keyset attributes from a record
25
+ def keyset_attributes_from(record) = record.to_hash.slice(*@keyset.keys)
26
+
27
+ # Get the hash of quoted keyset identifiers
28
+ def quoted_identifiers(table)
29
+ db = @set.db
30
+ @keyset.to_h { |column| [column, "#{db.quote_identifier(table)}.#{db.quote_identifier(column)}"] }
31
+ end
32
+
33
+ # Typecast the attributes
34
+ def typecast(attributes)
35
+ model = @set.opts[:model]
36
+ model.unrestrict_primary_key if (primary_key_is_restricted = model.restrict_primary_key?)
37
+ attributes = model.new(attributes).to_hash.slice(*attributes.keys.map(&:to_sym))
38
+ model.restrict_primary_key if primary_key_is_restricted
39
+ attributes
40
+ end
41
+
42
+ # Append the missing keyset keys, if the set is restricted by select
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
+ # Apply the where predicate to the set
51
+ def apply_where(predicate, arguments)
52
+ @set = @set.where(::Sequel.lit(predicate, **arguments))
53
+ end
54
+
55
+ def self.included(including)
56
+ instance_methods(false).each do |method_name|
57
+ including.send(:protected, method_name)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../adapters/active_record'
4
+
5
+ class Pagy
6
+ class Keyset
7
+ class Keynav
8
+ class ActiveRecord < Keynav
9
+ include Adapters::ActiveRecord
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../adapters/sequel'
4
+
5
+ class Pagy
6
+ class Keyset
7
+ class Keynav
8
+ class Sequel < Keynav
9
+ include Adapters::Sequel
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Pagy
4
+ class Keyset
5
+ # Use keyset pagination with resources for all the frontend helpers
6
+ class Keynav < Keyset
7
+ # Avoid conflicts between filter arguments in composite SQL fragments
8
+ PRIOR_PREFIX = 'prior_'
9
+ PAGE_PREFIX = 'page_'
10
+
11
+ path = Pathname.new(__FILE__).sub_ext('')
12
+ autoload :ActiveRecord, path.join('active_record')
13
+ autoload :Sequel, path.join('sequel')
14
+
15
+ # Finalize the instance variables needed for the UI
16
+ def initialize(set, **)
17
+ super
18
+ # Ensure next is called, so the last page used by the UI helpers is known
19
+ self.next
20
+ @previous = @page - 1 unless @page == 1
21
+ @in = @records.size
22
+ end
23
+
24
+ attr_reader :update, :previous, :last
25
+
26
+ # Prepare the @update for the client when it's a new page, and return the next page number
27
+ def next
28
+ records
29
+ return if !@more || (@options[:max_pages] && @page >= @options[:max_pages])
30
+
31
+ @next ||= begin
32
+ unless @page_cutoff
33
+ @page_cutoff = extract_cutoff
34
+ @last += 1 # reflect the added cutoff
35
+ @update.push(@last, [@page, 1, @page_cutoff]) # last + splice arguments for the client cutoffs
36
+ end
37
+ @page + 1
38
+ end
39
+ end
40
+
41
+ protected
42
+
43
+ def keynav? = true
44
+
45
+ # Process the page array
46
+ def assign_page
47
+ if @options[:page]
48
+ storage_key, @page, @last, @prior_cutoff, @page_cutoff = @options[:page]
49
+ else
50
+ @page = @last = 1
51
+ end
52
+ @update = [storage_key, @options[:page_key]]
53
+ end
54
+
55
+ # Use a compound predicate to fetch the records
56
+ def fetch_records
57
+ return super unless @page_cutoff # last page
58
+
59
+ # Compound predicate for visited pages
60
+ predicate = +''
61
+ arguments = {}
62
+ if @prior_cutoff # not the first page
63
+ # Include the records after @prior_cutoff
64
+ predicate << "(#{compose_predicate(PRIOR_PREFIX)}) AND "
65
+ arguments.merge!(arguments_from(@prior_cutoff, PRIOR_PREFIX))
66
+ end
67
+ # Exclude the records after @page_cutoff
68
+ predicate << "NOT (#{compose_predicate(PAGE_PREFIX)})"
69
+ arguments.merge!(arguments_from(@page_cutoff, PAGE_PREFIX))
70
+ apply_where(predicate, arguments)
71
+
72
+ @more = true # not the last page
73
+ @set.limit(nil).to_a # replaced by the compound predicate
74
+ end
75
+ end
76
+ end
77
+ end