pagy 5.7.5 → 8.6.2

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 (153) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +1 -1
  3. data/apps/calendar.ru +745 -0
  4. data/apps/demo.ru +435 -0
  5. data/apps/rails.ru +213 -0
  6. data/apps/repro.ru +177 -0
  7. data/apps/tmp/calendar.sqlite3 +0 -0
  8. data/apps/tmp/calendar.sqlite3-shm +0 -0
  9. data/apps/tmp/calendar.sqlite3-wal +0 -0
  10. data/apps/tmp/local_secret.txt +1 -0
  11. data/bin/pagy +100 -0
  12. data/{lib/config → config}/pagy.rb +72 -96
  13. data/javascripts/pagy-module.js +100 -0
  14. data/javascripts/pagy.d.ts +5 -0
  15. data/javascripts/pagy.js +4 -0
  16. data/javascripts/pagy.min.js +4 -0
  17. data/javascripts/pagy.min.js.map +10 -0
  18. data/javascripts/pagy.mjs +100 -0
  19. data/lib/optimist.rb +1022 -0
  20. data/lib/pagy/backend.rb +16 -4
  21. data/lib/pagy/calendar/day.rb +22 -10
  22. data/lib/pagy/calendar/month.rb +35 -9
  23. data/lib/pagy/calendar/quarter.rb +36 -10
  24. data/lib/pagy/calendar/unit.rb +106 -0
  25. data/lib/pagy/calendar/week.rb +16 -16
  26. data/lib/pagy/calendar/year.rb +15 -9
  27. data/lib/pagy/calendar.rb +60 -68
  28. data/lib/pagy/console.rb +3 -3
  29. data/lib/pagy/countless.rb +17 -12
  30. data/lib/pagy/exceptions.rb +1 -1
  31. data/lib/pagy/extras/arel.rb +2 -2
  32. data/lib/pagy/extras/array.rb +2 -2
  33. data/lib/pagy/extras/bootstrap.rb +57 -53
  34. data/lib/pagy/extras/bulma.rb +53 -57
  35. data/lib/pagy/extras/calendar.rb +54 -37
  36. data/lib/pagy/extras/countless.rb +3 -3
  37. data/lib/pagy/extras/elasticsearch_rails.rb +12 -11
  38. data/lib/pagy/extras/foundation.rb +59 -54
  39. data/lib/pagy/extras/gearbox.rb +32 -19
  40. data/lib/pagy/extras/headers.rb +11 -11
  41. data/lib/pagy/extras/i18n.rb +5 -5
  42. data/lib/pagy/extras/items.rb +34 -24
  43. data/lib/pagy/extras/{frontend_helpers.rb → js_tools.rb} +24 -24
  44. data/lib/pagy/extras/jsonapi.rb +79 -0
  45. data/lib/pagy/extras/materialize.rb +63 -47
  46. data/lib/pagy/extras/meilisearch.rb +26 -22
  47. data/lib/pagy/extras/metadata.rb +13 -9
  48. data/lib/pagy/extras/overflow.rb +13 -10
  49. data/lib/pagy/extras/pagy.rb +82 -0
  50. data/lib/pagy/extras/searchkick.rb +12 -11
  51. data/lib/pagy/extras/semantic.rb +57 -45
  52. data/lib/pagy/extras/size.rb +40 -0
  53. data/lib/pagy/extras/standalone.rb +12 -16
  54. data/lib/pagy/extras/trim.rb +13 -13
  55. data/lib/pagy/extras/uikit.rb +60 -46
  56. data/lib/pagy/frontend.rb +62 -45
  57. data/lib/pagy/i18n.rb +4 -3
  58. data/lib/pagy/url_helpers.rb +13 -25
  59. data/lib/pagy.rb +61 -59
  60. data/locales/ar.yml +28 -0
  61. data/locales/be.yml +25 -0
  62. data/locales/bg.yml +21 -0
  63. data/locales/bs.yml +25 -0
  64. data/locales/ca.yml +21 -0
  65. data/locales/ckb.yml +18 -0
  66. data/locales/cs.yml +23 -0
  67. data/locales/da.yml +21 -0
  68. data/locales/de.yml +21 -0
  69. data/locales/en.yml +21 -0
  70. data/locales/es.yml +21 -0
  71. data/locales/fr.yml +21 -0
  72. data/locales/hr.yml +25 -0
  73. data/locales/id.yml +19 -0
  74. data/locales/it.yml +21 -0
  75. data/locales/ja.yml +19 -0
  76. data/locales/km.yml +19 -0
  77. data/locales/ko.yml +17 -0
  78. data/locales/nb.yml +21 -0
  79. data/locales/nl.yml +21 -0
  80. data/locales/nn.yml +21 -0
  81. data/locales/pl.yml +25 -0
  82. data/locales/pt-BR.yml +21 -0
  83. data/locales/pt.yml +21 -0
  84. data/locales/ru.yml +25 -0
  85. data/locales/sr.yml +25 -0
  86. data/locales/sv-SE.yml +21 -0
  87. data/locales/sv.yml +21 -0
  88. data/locales/sw.yml +23 -0
  89. data/locales/ta.yml +23 -0
  90. data/locales/tr.yml +19 -0
  91. data/locales/uk.yml +25 -0
  92. data/locales/vi.yml +17 -0
  93. data/locales/zh-CN.yml +19 -0
  94. data/locales/zh-HK.yml +19 -0
  95. data/locales/zh-TW.yml +19 -0
  96. data/stylesheets/pagy.css +46 -0
  97. data/stylesheets/pagy.scss +48 -0
  98. data/stylesheets/pagy.tailwind.css +21 -0
  99. metadata +79 -66
  100. data/lib/javascripts/pagy-dev.js +0 -118
  101. data/lib/javascripts/pagy-module.d.ts +0 -30
  102. data/lib/javascripts/pagy-module.js +0 -117
  103. data/lib/javascripts/pagy.js +0 -1
  104. data/lib/locales/ar.yml +0 -26
  105. data/lib/locales/bg.yml +0 -22
  106. data/lib/locales/bs.yml +0 -24
  107. data/lib/locales/ca.yml +0 -22
  108. data/lib/locales/cs.yml +0 -22
  109. data/lib/locales/da.yml +0 -22
  110. data/lib/locales/de.yml +0 -22
  111. data/lib/locales/en.yml +0 -22
  112. data/lib/locales/es.yml +0 -22
  113. data/lib/locales/fr.yml +0 -22
  114. data/lib/locales/hr.yml +0 -24
  115. data/lib/locales/id.yml +0 -20
  116. data/lib/locales/it.yml +0 -22
  117. data/lib/locales/ja.yml +0 -20
  118. data/lib/locales/km.yml +0 -19
  119. data/lib/locales/ko.yml +0 -20
  120. data/lib/locales/nb.yml +0 -22
  121. data/lib/locales/nl.yml +0 -22
  122. data/lib/locales/pl.yml +0 -24
  123. data/lib/locales/pt-BR.yml +0 -22
  124. data/lib/locales/pt.yml +0 -22
  125. data/lib/locales/ru.yml +0 -24
  126. data/lib/locales/sr.yml +0 -23
  127. data/lib/locales/sv-SE.yml +0 -23
  128. data/lib/locales/sv.yml +0 -23
  129. data/lib/locales/sw.yml +0 -22
  130. data/lib/locales/ta.yml +0 -22
  131. data/lib/locales/tr.yml +0 -20
  132. data/lib/locales/uk.yml +0 -24
  133. data/lib/locales/zh-CN.yml +0 -20
  134. data/lib/locales/zh-HK.yml +0 -20
  135. data/lib/locales/zh-TW.yml +0 -20
  136. data/lib/pagy/calendar/month_mixin.rb +0 -49
  137. data/lib/pagy/extras/navs.rb +0 -63
  138. data/lib/pagy/extras/support.rb +0 -54
  139. data/lib/templates/bootstrap_nav.html.erb +0 -24
  140. data/lib/templates/bootstrap_nav.html.haml +0 -34
  141. data/lib/templates/bootstrap_nav.html.slim +0 -34
  142. data/lib/templates/bulma_nav.html.erb +0 -24
  143. data/lib/templates/bulma_nav.html.haml +0 -32
  144. data/lib/templates/bulma_nav.html.slim +0 -32
  145. data/lib/templates/foundation_nav.html.erb +0 -24
  146. data/lib/templates/foundation_nav.html.haml +0 -34
  147. data/lib/templates/foundation_nav.html.slim +0 -34
  148. data/lib/templates/nav.html.erb +0 -22
  149. data/lib/templates/nav.html.haml +0 -30
  150. data/lib/templates/nav.html.slim +0 -29
  151. data/lib/templates/uikit_nav.html.erb +0 -15
  152. data/lib/templates/uikit_nav.html.haml +0 -28
  153. data/lib/templates/uikit_nav.html.slim +0 -28
@@ -1,88 +1,93 @@
1
- # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/foundation
2
1
  # frozen_string_literal: true
3
2
 
4
- require 'pagy/extras/frontend_helpers'
3
+ warn '[PAGY WARNING] The foundation extra has been discontinued and it will be removed in v9 ' \
4
+ '(https://github.com/ddnexus/pagy/discussions/672#discussioncomment-9212328)'
5
+
6
+ require_relative 'js_tools'
5
7
 
6
8
  class Pagy # :nodoc:
7
9
  # Frontend modules are specially optimized for performance.
8
10
  # The resulting code may not look very elegant, but produces the best benchmarks
9
11
  module FoundationExtra
10
12
  # Pagination for Foundation: it returns the html with the series of links to the pages
11
- def pagy_foundation_nav(pagy, pagy_id: nil, link_extra: '', **vars)
12
- p_id = %( id="#{pagy_id}") if pagy_id
13
- link = pagy_link_proc(pagy, link_extra: link_extra)
13
+ def pagy_foundation_nav(pagy, id: nil, aria_label: nil, **vars)
14
+ id = %( id="#{id}") if id
15
+ a = pagy_anchor(pagy)
14
16
 
15
- html = +%(<nav#{p_id} class="pagy-foundation-nav" aria-label="Pagination"><ul class="pagination">)
16
- html << pagy_foundation_prev_html(pagy, link)
17
+ html = +%(<nav#{id} class="pagy-foundation-nav" #{
18
+ nav_aria_label(pagy, aria_label:)}><ul class="pagination"> #{
19
+ foundation_prev_html(pagy, a)})
17
20
  pagy.series(**vars).each do |item| # series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]
18
21
  html << case item
19
- when Integer then %(<li>#{link.call item}</li>) # page link
20
- when String then %(<li class="current">#{pagy.label_for(item)}</li>) # active page
21
- when :gap then %(<li class="ellipsis gap" aria-hidden="true"></li>) # page gap
22
- else raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
22
+ when Integer
23
+ %(<li>#{a.(item)}</li>)
24
+ when String
25
+ %(<li class="current" role="link" aria-current="page" aria-disabled="true">#{pagy.label_for(item)}</li>)
26
+ when :gap
27
+ %(<li class="ellipsis gap"></li>)
28
+ else
29
+ raise InternalError, "expected item types in series to be Integer, String or :gap; got #{item.inspect}"
23
30
  end
24
31
  end
25
- html << pagy_foundation_next_html(pagy, link)
26
- html << %(</ul></nav>)
32
+ html << %(#{foundation_next_html(pagy, a)}</ul></nav>)
27
33
  end
28
34
 
29
- # Javascript pagination for foundation: it returns a nav and a JSON tag used by the pagy.js file
30
- def pagy_foundation_nav_js(pagy, pagy_id: nil, link_extra: '', **vars)
35
+ # Javascript pagination for foundation: it returns a nav with a data-pagy attribute used by the pagy.js file
36
+ def pagy_foundation_nav_js(pagy, id: nil, aria_label: nil, **vars)
31
37
  sequels = pagy.sequels(**vars)
32
- p_id = %( id="#{pagy_id}") if pagy_id
33
- link = pagy_link_proc(pagy, link_extra: link_extra)
34
- tags = { 'before' => %(<ul class="pagination">#{pagy_foundation_prev_html pagy, link}),
35
- 'link' => %(<li>#{link.call(PAGE_PLACEHOLDER, LABEL_PLACEHOLDER)}</li>),
36
- 'active' => %(<li class="current">#{LABEL_PLACEHOLDER}</li>),
37
- 'gap' => %(<li class="ellipsis gap" aria-hidden="true"></li>),
38
- 'after' => %(#{pagy_foundation_next_html pagy, link}</ul>) }
38
+ id = %( id="#{id}") if id
39
+ a = pagy_anchor(pagy)
40
+ tokens = { 'before' => %(<ul class="pagination">#{foundation_prev_html pagy, a}),
41
+ 'a' => %(<li>#{a.(PAGE_TOKEN, LABEL_TOKEN)}</li>),
42
+ 'current' => %(<li class="current" role="link" aria-current="page" aria-disabled="true">#{LABEL_TOKEN}</li>),
43
+ 'gap' => %(<li class="ellipsis gap"></li>),
44
+ 'after' => %(#{foundation_next_html pagy, a}</ul>) }
39
45
 
40
- %(<nav#{p_id} class="#{'pagy-rjs ' if sequels.size > 1}pagy-foundation-nav-js" aria-label="Pagination" #{
41
- pagy_json_attr(pagy, :nav, tags, sequels, pagy.label_sequels(sequels))}></nav>)
46
+ %(<nav#{id} class="#{'pagy-rjs ' if sequels.size > 1}pagy-foundation-nav-js" #{
47
+ nav_aria_label(pagy, aria_label:)} #{
48
+ pagy_data(pagy, :nav, tokens, sequels, pagy.label_sequels(sequels))
49
+ }></nav>)
42
50
  end
43
51
 
44
- # Javascript combo pagination for Foundation: it returns a nav and a JSON tag used by the pagy.js file
45
- def pagy_foundation_combo_nav_js(pagy, pagy_id: nil, link_extra: '')
46
- p_id = %( id="#{pagy_id}") if pagy_id
47
- link = pagy_link_proc(pagy, link_extra: link_extra)
48
- p_page = pagy.page
49
- p_pages = pagy.pages
50
- input = %(<input class="input-group-field cell shrink" type="number" min="1" max="#{
51
- p_pages}" value="#{p_page}" style="width: #{
52
- p_pages.to_s.length + 1}rem; padding: 0 0.3rem; margin: 0 0.3rem;">)
52
+ # Javascript combo pagination for Foundation: it returns a nav with a data-pagy attribute used by the pagy.js file
53
+ def pagy_foundation_combo_nav_js(pagy, id: nil, aria_label: nil)
54
+ id = %( id="#{id}") if id
55
+ a = pagy_anchor(pagy)
56
+ pages = pagy.pages
57
+
58
+ page_input = %(<input name="page" type="number" min="1" max="#{pages}" value="#{pagy.page}" aria-current="page" ) <<
59
+ %(style="text-align: center; width: #{pages.to_s.length + 1}rem; ) <<
60
+ %(height: 1.5rem; padding: .5rem; margin: 0 .4rem; font-size: .875rem;" class="current">#{JSTools::A_TAG})
53
61
 
54
- %(<nav#{p_id} class="pagy-foundation-combo-nav-js" aria-label="Pagination"><div class="input-group" #{
55
- pagy_json_attr pagy, :combo, pagy_marked_link(link)}>#{
56
- if (p_prev = pagy.prev)
57
- link.call p_prev, pagy_t('pagy.nav.prev'),
58
- 'style="margin-bottom: 0" aria-label="previous" class="prev button primary"'
59
- else
60
- %(<a style="margin-bottom: 0" class="prev button primary disabled" href="#">#{pagy_t 'pagy.nav.prev'}</a>)
61
- end
62
- }<span class="input-group-label">#{pagy_t 'pagy.combo_nav_js', page_input: input, count: p_page, pages: p_pages}</span>#{
63
- if (p_next = pagy.next)
64
- link.call p_next, pagy_t('pagy.nav.next'), 'style="margin-bottom: 0" aria-label="next" class="next button primary"'
65
- else
66
- %(<a style="margin-bottom: 0" class="next button primary disabled" href="#">#{pagy_t 'pagy.nav.next'}</a>)
67
- end
68
- }</div></nav>)
62
+ %(<nav#{id} class="pagy-foundation-combo-nav-js" #{
63
+ nav_aria_label(pagy, aria_label:)} #{
64
+ pagy_data(pagy, :combo, pagy_url_for(pagy, PAGE_TOKEN))
65
+ }><ul class="pagination">#{
66
+ foundation_prev_html(pagy, a)
67
+ }<li style="padding: 0 .3rem"><label style="display: flex; align-items: center; white-space: nowrap">#{
68
+ pagy_t('pagy.combo_nav_js', page_input:, pages:)
69
+ }</label></li>#{
70
+ foundation_next_html(pagy, a)
71
+ }</ul></nav>)
69
72
  end
70
73
 
71
74
  private
72
75
 
73
- def pagy_foundation_prev_html(pagy, link)
76
+ def foundation_prev_html(pagy, a)
74
77
  if (p_prev = pagy.prev)
75
- %(<li class="prev">#{link.call p_prev, pagy_t('pagy.nav.prev'), 'aria-label="previous"'}</li>)
78
+ %(<li class="prev">#{a.(p_prev, pagy_t('pagy.prev'), aria_label: pagy_t('pagy.aria_label.prev'))}</li>)
76
79
  else
77
- %(<li class="prev disabled">#{pagy_t 'pagy.nav.prev'}</li>)
80
+ %(<li class="prev disabled" role="link" aria-disabled="true" aria-label="#{
81
+ pagy_t('pagy.aria_label.prev')}">#{pagy_t('pagy.prev')}</li>)
78
82
  end
79
83
  end
80
84
 
81
- def pagy_foundation_next_html(pagy, link)
85
+ def foundation_next_html(pagy, a)
82
86
  if (p_next = pagy.next)
83
- %(<li class="next">#{link.call p_next, pagy_t('pagy.nav.next'), 'aria-label="next"'}</li>)
87
+ %(<li class="next">#{a.(p_next, pagy_t('pagy.next'), aria_label: pagy_t('pagy.aria_label.next'))}</li>)
84
88
  else
85
- %(<li class="next disabled">#{pagy_t 'pagy.nav.next'}</li>)
89
+ %(<li class="next disabled" role="link" aria-disabled="true" aria-label="#{
90
+ pagy_t('pagy.aria_label.next')}">#{pagy_t('pagy.next')}</li>)
86
91
  end
87
92
  end
88
93
  end
@@ -1,4 +1,4 @@
1
- # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/gearbox
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/gearbox
2
2
  # frozen_string_literal: true
3
3
 
4
4
  class Pagy # :nodoc:
@@ -12,30 +12,43 @@ class Pagy # :nodoc:
12
12
  def setup_items_var
13
13
  return super if !@vars[:gearbox_extra] || @vars[:items_extra]
14
14
 
15
- gearbox_items = @vars[:gearbox_items]
16
- raise VariableError.new(self, :gearbox_items, 'to be an Array of positives', gearbox_items) \
17
- unless gearbox_items.is_a?(Array) && gearbox_items.all? { |num| num.positive? rescue false } # rubocop:disable Style/RescueModifier
15
+ gears = @vars[:gearbox_items]
16
+ raise VariableError.new(self, :gearbox_items, 'to be an Array of positives', gears) \
17
+ unless gears.is_a?(Array) && gears.all? { |num| num.positive? rescue false } # rubocop:disable Style/RescueModifier
18
18
 
19
- @items = gearbox_items[@page - 1] || gearbox_items.last
19
+ @items = gears[@page - 1] || gears.last
20
20
  end
21
21
 
22
- # Setup @pages and @last based on the :gearbox_items variable
23
- def setup_pages_var
22
+ # Setup @offset based on the :gearbox_items variable
23
+ def setup_offset_var
24
24
  return super if !@vars[:gearbox_extra] || @vars[:items_extra]
25
25
 
26
- gearbox_items = @vars[:gearbox_items]
26
+ gears = @vars[:gearbox_items]
27
+ @offset = if @page <= gears.count
28
+ gears[0, @page - 1].sum
29
+ else
30
+ gears.sum + (gears.last * (@page - gears.count - 1))
31
+ end + @outset
32
+ end
33
+
34
+ # Setup Pagy @last based on the :gearbox_items variable and @count
35
+ def setup_last_var
36
+ return super if !@vars[:gearbox_extra] || @vars[:items_extra]
37
+
38
+ gears = @vars[:gearbox_items]
27
39
  # This algorithm is thousands of times faster than the one in the geared_pagination gem
28
- @pages = @last = (if @count > (sum = gearbox_items.sum)
29
- [((@count - sum).to_f / gearbox_items.last).ceil, 1].max + gearbox_items.count
30
- else
31
- pages = 0
32
- remainder = @count
33
- while remainder.positive?
34
- pages += 1
35
- remainder -= gearbox_items[pages - 1]
36
- end
37
- [pages, 1].max
38
- end)
40
+ @last = (if count > (sum = gears.sum)
41
+ [((count - sum).to_f / gears.last).ceil, 1].max + gears.count
42
+ else
43
+ pages = 0
44
+ remainder = count
45
+ while remainder.positive?
46
+ pages += 1
47
+ remainder -= gears[pages - 1]
48
+ end
49
+ [pages, 1].max
50
+ end)
51
+ @last = vars[:max_pages] if vars[:max_pages] && @last > vars[:max_pages]
39
52
  end
40
53
  end
41
54
  prepend GearboxExtra
@@ -1,13 +1,13 @@
1
- # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/headers
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/headers
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'pagy/url_helpers'
4
+ require_relative '../url_helpers'
5
5
 
6
6
  class Pagy # :nodoc:
7
- DEFAULT[:headers] = { page: 'Current-Page',
8
- items: 'Page-Items',
9
- count: 'Total-Count',
10
- pages: 'Total-Pages' }
7
+ DEFAULT[:headers] = { page: 'current-page',
8
+ items: 'page-items',
9
+ count: 'total-count',
10
+ pages: 'total-pages' }
11
11
  # Add specialized backend methods to add pagination response headers
12
12
  module HeadersExtra
13
13
  include UrlHelpers
@@ -22,7 +22,7 @@ class Pagy # :nodoc:
22
22
  # Generate a hash of RFC-8288 compliant http headers
23
23
  def pagy_headers(pagy)
24
24
  pagy_headers_hash(pagy).tap do |hash|
25
- hash['Link'] = hash['Link'].map { |rel, link| %(<#{link}>; rel="#{rel}") }.join(', ')
25
+ hash['link'] = hash['link'].map { |rel, link| %(<#{link}>; rel="#{rel}") }.join(', ')
26
26
  end
27
27
  end
28
28
 
@@ -31,12 +31,12 @@ class Pagy # :nodoc:
31
31
  countless = defined?(Countless) && pagy.is_a?(Countless)
32
32
  rel = { 'first' => 1, 'prev' => pagy.prev, 'next' => pagy.next }
33
33
  rel['last'] = pagy.last unless countless
34
- url_str = pagy_url_for(pagy, PAGE_PLACEHOLDER, absolute: true)
35
- link = rel.map do |r, num| # filter_map if ruby >=2.7
34
+ url_str = pagy_url_for(pagy, PAGE_TOKEN, absolute: true)
35
+ link = rel.filter_map do |r, num|
36
36
  next unless num # rubocop:disable Layout/EmptyLineAfterGuardClause
37
- [r, url_str.sub(PAGE_PLACEHOLDER, num.to_s)]
37
+ [r, url_str.sub(PAGE_TOKEN, num.to_s)]
38
38
  end.compact.to_h
39
- hash = { 'Link' => link }
39
+ hash = { 'link' => link }
40
40
  headers = pagy.vars[:headers]
41
41
  hash[headers[:page]] = pagy.page.to_s if headers[:page]
42
42
  if headers[:items] && !(defined?(Calendar) && pagy.is_a?(Calendar)) # items is not for Calendar
@@ -1,25 +1,25 @@
1
- # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/i18n
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/i18n
2
2
  # frozen_string_literal: true
3
3
 
4
4
  class Pagy # :nodoc:
5
5
  # Use ::I18n gem
6
6
  module I18nExtra
7
7
  # Frontend overriding for translation
8
- module Frontend
8
+ module FrontendOverride
9
9
  def pagy_t(key, **opts)
10
10
  ::I18n.t(key, **opts)
11
11
  end
12
12
  end
13
+ Frontend.prepend I18nExtra::FrontendOverride
13
14
 
14
15
  # Calendar overriding for localization (see also the block in the calendar section of the config/pagy.rb initializer)
15
- module Calendar
16
+ module CalendarOverride
16
17
  def localize(time, opts)
17
18
  ::I18n.l(time, **opts)
18
19
  end
19
20
  end
20
21
  end
21
- Frontend.prepend I18nExtra::Frontend
22
- Calendar.prepend I18nExtra::Calendar if defined?(Calendar)
22
+ Calendar::Unit.prepend I18nExtra::CalendarOverride if defined?(Calendar::Unit)
23
23
 
24
24
  # Add the pagy locales to the I18n.load_path
25
25
  ::I18n.load_path += Dir[Pagy.root.join('locales', '*.yml')]
@@ -1,7 +1,7 @@
1
- # See the Pagy documentation: https://ddnexus.github.io/pagy/extras/items
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/items
2
2
  # frozen_string_literal: true
3
3
 
4
- require 'pagy/extras/frontend_helpers'
4
+ require_relative 'js_tools'
5
5
 
6
6
  class Pagy # :nodoc:
7
7
  DEFAULT[:items_param] = :items
@@ -11,44 +11,54 @@ class Pagy # :nodoc:
11
11
  # Allow the client to request a custom number of items per page with an optional selector UI
12
12
  module ItemsExtra
13
13
  # Additions for the Backend module
14
- module Backend
14
+ module BackendAddOn
15
15
  private
16
16
 
17
17
  # Set the items variable considering the params and other pagy variables
18
18
  def pagy_set_items_from_params(vars)
19
19
  return if vars[:items] # :items explicitly set
20
20
  return unless vars.key?(:items_extra) ? vars[:items_extra] : DEFAULT[:items_extra] # :items_extra is false
21
- return unless (items = params[vars[:items_param] || DEFAULT[:items_param]]) # no items from request params
21
+ return unless (items_count = pagy_get_items_size(vars)) # no items from request params
22
22
 
23
- vars[:items] = [items.to_i, vars.key?(:max_items) ? vars[:max_items] : DEFAULT[:max_items]].compact.min
23
+ vars[:items] = [items_count.to_i, vars.key?(:max_items) ? vars[:max_items] : DEFAULT[:max_items]].compact.min
24
+ end
25
+
26
+ # Get the items count from the params
27
+ # Overridable by the jsonapi extra
28
+ def pagy_get_items_size(vars)
29
+ params[vars[:items_param] || DEFAULT[:items_param]]
24
30
  end
25
31
  end
32
+ Backend.prepend ItemsExtra::BackendAddOn
26
33
 
27
34
  # Additions for the Frontend module
28
- module Frontend
29
- ITEMS_PLACEHOLDER = '__pagy_items__'
35
+ module FrontendAddOn
36
+ ITEMS_TOKEN = '__pagy_items__'
30
37
 
31
38
  # Return the items selector HTML. For example "Show [20] items per page"
32
- def pagy_items_selector_js(pagy, pagy_id: nil, item_name: nil, i18n_key: nil, link_extra: '')
39
+ def pagy_items_selector_js(pagy, id: nil, item_name: nil)
33
40
  return '' unless pagy.vars[:items_extra]
34
41
 
35
- p_id = %( id="#{pagy_id}") if pagy_id
36
- p_vars = pagy.vars
37
- p_items = p_vars[:items]
38
- p_vars[:items] = ITEMS_PLACEHOLDER
39
- link = pagy_marked_link(pagy_link_proc(pagy, link_extra: link_extra))
40
- p_vars[:items] = p_items # restore the items
41
-
42
- html = +%(<span#{p_id} class="pagy-items-selector-js" #{pagy_json_attr pagy, :selector, pagy.from, link}>)
43
- input = %(<input type="number" min="1" max="#{p_vars[:max_items]}" value="#{
44
- p_items}" style="padding: 0; text-align: center; width: #{p_items.to_s.length + 1}rem;">)
45
- html << pagy_t('pagy.items_selector_js', item_name: item_name || pagy_t(i18n_key || p_vars[:i18n_key], count: p_items),
46
- items_input: input,
47
- count: p_items)
48
- html << %(</span>)
42
+ id = %( id="#{id}") if id
43
+ vars = pagy.vars
44
+ items = vars[:items]
45
+ vars[:items] = ITEMS_TOKEN
46
+ url_token = pagy_url_for(pagy, PAGE_TOKEN)
47
+ vars[:items] = items # restore the items
48
+
49
+ items_input = %(<input name="items" type="number" min="1" max="#{vars[:max_items]}" value="#{
50
+ items}" style="padding: 0; text-align: center; width: #{items.to_s.length + 1}rem;">#{JSTools::A_TAG})
51
+
52
+ %(<span#{id} class="pagy items-selector-js" #{
53
+ pagy_data(pagy, :selector, pagy.from, url_token)
54
+ }><label>#{
55
+ pagy_t('pagy.items_selector_js',
56
+ item_name: item_name || pagy_t('pagy.item_name', count: items),
57
+ items_input:,
58
+ count: items)
59
+ }</label></span>)
49
60
  end
50
61
  end
62
+ Frontend.prepend ItemsExtra::FrontendAddOn
51
63
  end
52
- Backend.prepend ItemsExtra::Backend
53
- Frontend.prepend ItemsExtra::Frontend
54
64
  end
@@ -1,22 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'digest'
4
-
5
3
  class Pagy # :nodoc:
6
4
  DEFAULT[:steps] = false # default false will use {0 => @vars[:size]}
7
5
 
8
6
  # Private module documented in the main classes
9
- module FrontendHelpers
7
+ module JSTools
8
+ # Dummy tag for input helpers: needed because Turbo does not intercept window.location changes
9
+ A_TAG = '<a href="#" style="display: none;">#</a>'
10
+
10
11
  # Additions for the Pagy class
11
- module Pagy
12
+ module PagyAddOn
12
13
  # `Pagy` instance method used by the `pagy*_nav_js` helpers.
13
14
  # It returns the sequels of width/series generated from the :steps hash
14
15
  # Example:
15
- # >> pagy = Pagy.new(count:1000, page: 20, steps: {0 => [1,2,2,1], 350 => [2,3,3,2], 550 => [3,4,4,3]})
16
+ # >> pagy = Pagy.new(count:1000, page: 20, steps: {0 => 5, 350 => 7, 550 => 9})
16
17
  # >> pagy.sequels
17
- # #=> { "0" => [1, :gap, 18, 19, "20", 21, 22, :gap, 50],
18
- # "350" => [1, 2, :gap, 17, 18, 19, "20", 21, 22, 23, :gap, 49, 50],
19
- # "550" => [1, 2, 3, :gap, 16, 17, 18, 19, "20", 21, 22, 23, 24, :gap, 48, 49, 50] }
18
+ # #=> { "0" => [18, 19, "20", 21, 22],
19
+ # "350" => [1, :gap, 19, "20", 21, :gap, 50],
20
+ # "550" => [1 :gap, 18, 19, "20", 21, 22, :gap, 50] }
20
21
  # Notice: if :steps is false it will use the single {0 => @vars[:size]} size
21
22
  def sequels(steps: @vars[:steps] || { 0 => @vars[:size] }, **_)
22
23
  raise VariableError.new(self, :steps, 'to define the 0 width', steps) unless steps.key?(0)
@@ -29,9 +30,10 @@ class Pagy # :nodoc:
29
30
  # Support for the Calendar API
30
31
  def label_sequels(*); end
31
32
  end
33
+ Pagy.prepend PagyAddOn
32
34
 
33
35
  # Additions for Calendar class
34
- module Calendar
36
+ module CalendarOverride
35
37
  def label_sequels(sequels = self.sequels)
36
38
  {}.tap do |label_sequels|
37
39
  sequels.each do |width, series|
@@ -40,31 +42,29 @@ class Pagy # :nodoc:
40
42
  end
41
43
  end
42
44
  end
45
+ Calendar::Unit.prepend CalendarOverride if defined?(Calendar::Unit)
43
46
 
44
47
  # Additions for the Frontend
45
- module Frontend
48
+ module FrontendAddOn
46
49
  if defined?(Oj)
47
- # Return a script tag with the JSON-serialized args generated with the faster oj gem
48
- def pagy_json_attr(pagy, *args)
50
+ # Return a data tag with the base64 encoded JSON-serialized args generated with the faster oj gem
51
+ # Base64 encoded JSON is smaller than HTML escaped JSON
52
+ def pagy_data(pagy, *args)
49
53
  args << pagy.vars[:page_param] if pagy.vars[:trim_extra]
50
- %(data-pagy-json="#{Oj.dump(args, mode: :strict).gsub('"', '&quot;')}")
54
+ strict_base64_encoded = [Oj.dump(args, mode: :strict)].pack('m0')
55
+ %(data-pagy="#{strict_base64_encoded}")
51
56
  end
52
57
  else
53
58
  require 'json'
54
- # Return a script tag with the JSON-serialized args generated with the slower to_json
55
- def pagy_json_attr(pagy, *args)
59
+ # Return a data tag with the base64 encoded JSON-serialized args generated with the slower to_json
60
+ # Base64 encoded JSON is smaller than HTML escaped JSON
61
+ def pagy_data(pagy, *args)
56
62
  args << pagy.vars[:page_param] if pagy.vars[:trim_extra]
57
- %(data-pagy-json="#{args.to_json.gsub('"', '&quot;')}")
63
+ strict_base64_encoded = [args.to_json].pack('m0')
64
+ %(data-pagy="#{strict_base64_encoded}")
58
65
  end
59
66
  end
60
-
61
- # Return the marked link to used by pagy.js
62
- def pagy_marked_link(link)
63
- link.call PAGE_PLACEHOLDER, '', 'style="display: none;"'
64
- end
65
67
  end
68
+ Frontend.prepend FrontendAddOn
66
69
  end
67
- prepend FrontendHelpers::Pagy
68
- Calendar.prepend FrontendHelpers::Calendar if defined?(Calendar)
69
- Frontend.prepend FrontendHelpers::Frontend
70
70
  end
@@ -0,0 +1,79 @@
1
+ # See the Pagy documentation: https://ddnexus.github.io/pagy/docs/extras/jsonapi
2
+ # frozen_string_literal: true
3
+
4
+ require_relative '../url_helpers'
5
+
6
+ class Pagy # :nodoc:
7
+ DEFAULT[:jsonapi] = true
8
+
9
+ # Add a specialized backend method compliant with JSON:API
10
+ module JsonApiExtra
11
+ # JsonApi :page param error
12
+ class ReservedParamError < StandardError
13
+ # Inform about the actual value
14
+ def initialize(value)
15
+ super("expected reserved :page param to be nil or Hash-like; got #{value.inspect}")
16
+ end
17
+ end
18
+
19
+ # Module overriding Backend
20
+ module BackendOverride
21
+ private
22
+
23
+ include UrlHelpers
24
+
25
+ # Return the jsonapi links
26
+ def pagy_jsonapi_links(pagy, **opts)
27
+ { first: pagy_url_for(pagy, 1, **opts),
28
+ last: pagy_url_for(pagy, pagy.last, **opts),
29
+ prev: pagy.prev ? pagy_url_for(pagy, pagy.prev, **opts) : nil,
30
+ next: pagy.next ? pagy_url_for(pagy, pagy.next, **opts) : nil }
31
+ end
32
+
33
+ # Should skip the jsonapi
34
+ def pagy_skip_jsonapi?(vars)
35
+ return true if vars[:jsonapi] == false || (vars[:jsonapi].nil? && DEFAULT[:jsonapi] == false)
36
+ # check the reserved :page param
37
+ raise ReservedParamError, params[:page] unless params[:page].respond_to?(:fetch) || params[:page].nil?
38
+ end
39
+
40
+ # Override the Backend method
41
+ def pagy_get_page(vars)
42
+ return super if pagy_skip_jsonapi?(vars)
43
+ return 1 if params[:page].nil?
44
+
45
+ [params[:page][vars[:page_param] || DEFAULT[:page_param]].to_i, 1].max
46
+ end
47
+ end
48
+ Backend.prepend BackendOverride
49
+
50
+ # Module overriding ItemsExtra
51
+ module ItemsExtraOverride
52
+ private
53
+
54
+ # Override the ItemsExtra::Backend method
55
+ def pagy_get_items_size(vars)
56
+ return super if pagy_skip_jsonapi?(vars)
57
+ return if params[:page].nil?
58
+
59
+ params[:page][vars[:items_param] || DEFAULT[:items_param]]
60
+ end
61
+ end
62
+ # :nocov:
63
+ ItemsExtra::BackendAddOn.prepend ItemsExtraOverride if defined?(ItemsExtra::BackendAddOn)
64
+ # :nocov:
65
+
66
+ # Module overriding UrlHelper
67
+ module UrlHelperOverride
68
+ # Override UrlHelper method
69
+ def pagy_set_query_params(page, vars, params)
70
+ return super unless vars[:jsonapi]
71
+
72
+ params['page'] ||= {}
73
+ params['page'][vars[:page_param].to_s] = page
74
+ params['page'][vars[:items_param].to_s] = vars[:items] if vars[:items_extra]
75
+ end
76
+ end
77
+ UrlHelpers.prepend UrlHelperOverride
78
+ end
79
+ end