pagy 3.8.2 → 9.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/apps/calendar.ru +737 -0
- data/apps/demo.ru +449 -0
- data/apps/index.rb +7 -0
- data/apps/keyset_ar.ru +228 -0
- data/apps/keyset_s.ru +220 -0
- data/apps/rails.ru +217 -0
- data/apps/repro.ru +182 -0
- data/bin/pagy +98 -0
- data/config/pagy.rb +220 -0
- data/javascripts/pagy.d.ts +5 -0
- data/javascripts/pagy.min.js +4 -0
- data/javascripts/pagy.min.js.map +10 -0
- data/javascripts/pagy.mjs +100 -0
- data/lib/optimist.rb +1022 -0
- data/lib/pagy/b64.rb +33 -0
- data/lib/pagy/backend.rb +30 -19
- data/lib/pagy/calendar/day.rb +41 -0
- data/lib/pagy/calendar/month.rb +42 -0
- data/lib/pagy/calendar/quarter.rb +49 -0
- data/lib/pagy/calendar/unit.rb +103 -0
- data/lib/pagy/calendar/week.rb +39 -0
- data/lib/pagy/calendar/year.rb +35 -0
- data/lib/pagy/calendar.rb +84 -0
- data/lib/pagy/console.rb +23 -0
- data/lib/pagy/countless.rb +27 -22
- data/lib/pagy/exceptions.rb +16 -13
- data/lib/pagy/extras/arel.rb +11 -14
- data/lib/pagy/extras/array.rb +12 -16
- data/lib/pagy/extras/bootstrap.rb +83 -41
- data/lib/pagy/extras/bulma.rb +79 -46
- data/lib/pagy/extras/calendar.rb +79 -0
- data/lib/pagy/extras/countless.rb +20 -25
- data/lib/pagy/extras/elasticsearch_rails.rb +59 -38
- data/lib/pagy/extras/gearbox.rb +55 -0
- data/lib/pagy/extras/headers.rb +38 -23
- data/lib/pagy/extras/i18n.rb +19 -18
- data/lib/pagy/extras/js_tools.rb +70 -0
- data/lib/pagy/extras/jsonapi.rb +88 -0
- data/lib/pagy/extras/keyset.rb +30 -0
- data/lib/pagy/extras/limit.rb +63 -0
- data/lib/pagy/extras/meilisearch.rb +57 -0
- data/lib/pagy/extras/metadata.rb +32 -27
- data/lib/pagy/extras/overflow.rb +61 -53
- data/lib/pagy/extras/pagy.rb +82 -0
- data/lib/pagy/extras/searchkick.rb +52 -41
- data/lib/pagy/extras/size.rb +40 -0
- data/lib/pagy/extras/standalone.rb +60 -0
- data/lib/pagy/extras/trim.rb +19 -13
- data/lib/pagy/frontend.rb +76 -51
- data/lib/pagy/i18n.rb +167 -0
- data/lib/pagy/keyset/active_record.rb +44 -0
- data/lib/pagy/keyset/sequel.rb +57 -0
- data/lib/pagy/keyset.rb +118 -0
- data/lib/pagy/shared_methods.rb +26 -0
- data/lib/pagy/url_helpers.rb +26 -0
- data/lib/pagy.rb +91 -37
- data/locales/ar.yml +29 -0
- data/locales/be.yml +25 -0
- data/locales/bg.yml +21 -0
- data/locales/bs.yml +25 -0
- data/locales/ca.yml +21 -0
- data/locales/ckb.yml +18 -0
- data/locales/cs.yml +23 -0
- data/locales/da.yml +21 -0
- data/locales/de.yml +21 -0
- data/locales/dz.yml +17 -0
- data/locales/en.yml +21 -0
- data/{lib/locales → locales}/es.yml +11 -12
- data/locales/fr.yml +21 -0
- data/locales/hr.yml +25 -0
- data/locales/id.yml +19 -0
- data/locales/it.yml +21 -0
- data/locales/ja.yml +19 -0
- data/locales/km.yml +19 -0
- data/locales/ko.yml +17 -0
- data/locales/nb.yml +21 -0
- data/locales/nl.yml +21 -0
- data/locales/nn.yml +21 -0
- data/locales/pl.yml +25 -0
- data/{lib/locales → locales}/pt-BR.yml +11 -12
- data/locales/pt.yml +21 -0
- data/locales/ru.yml +25 -0
- data/locales/sk.yml +23 -0
- data/locales/sr.yml +25 -0
- data/locales/sv-SE.yml +21 -0
- data/locales/sv.yml +21 -0
- data/locales/sw.yml +25 -0
- data/locales/ta.yml +21 -0
- data/locales/tr.yml +19 -0
- data/locales/uk.yml +25 -0
- data/locales/vi.yml +17 -0
- data/locales/zh-CN.yml +17 -0
- data/locales/zh-HK.yml +17 -0
- data/locales/zh-TW.yml +17 -0
- data/stylesheets/pagy.css +46 -0
- data/stylesheets/pagy.scss +48 -0
- data/stylesheets/pagy.tailwind.css +21 -0
- metadata +95 -67
- data/lib/config/pagy.rb +0 -170
- data/lib/javascripts/pagy.js +0 -106
- data/lib/locales/README.md +0 -35
- data/lib/locales/bg.yml +0 -22
- data/lib/locales/ca.yml +0 -22
- data/lib/locales/da.yml +0 -22
- data/lib/locales/de.yml +0 -22
- data/lib/locales/en.yml +0 -22
- data/lib/locales/fr.yml +0 -22
- data/lib/locales/id.yml +0 -20
- data/lib/locales/it.yml +0 -22
- data/lib/locales/ja.yml +0 -20
- data/lib/locales/km.yml +0 -19
- data/lib/locales/ko.yml +0 -20
- data/lib/locales/nb.yml +0 -22
- data/lib/locales/nl.yml +0 -22
- data/lib/locales/pl.yml +0 -24
- data/lib/locales/ru.yml +0 -24
- data/lib/locales/sv-SE.yml +0 -23
- data/lib/locales/sv.yml +0 -23
- data/lib/locales/tr.yml +0 -20
- data/lib/locales/utils/i18n.rb +0 -25
- data/lib/locales/utils/loader.rb +0 -34
- data/lib/locales/utils/p11n.rb +0 -80
- data/lib/locales/zh-CN.yml +0 -20
- data/lib/locales/zh-HK.yml +0 -20
- data/lib/locales/zh-TW.yml +0 -20
- data/lib/pagy/extras/foundation.rb +0 -57
- data/lib/pagy/extras/items.rb +0 -65
- data/lib/pagy/extras/materialize.rb +0 -59
- data/lib/pagy/extras/navs.rb +0 -38
- data/lib/pagy/extras/pagy_search.rb +0 -18
- data/lib/pagy/extras/semantic.rb +0 -55
- data/lib/pagy/extras/shared.rb +0 -53
- data/lib/pagy/extras/support.rb +0 -29
- data/lib/pagy/extras/uikit.rb +0 -62
- data/lib/templates/bootstrap_nav.html.erb +0 -24
- data/lib/templates/bootstrap_nav.html.haml +0 -34
- data/lib/templates/bootstrap_nav.html.slim +0 -34
- data/lib/templates/bulma_nav.html.erb +0 -24
- data/lib/templates/bulma_nav.html.haml +0 -32
- data/lib/templates/bulma_nav.html.slim +0 -32
- data/lib/templates/foundation_nav.html.erb +0 -24
- data/lib/templates/foundation_nav.html.haml +0 -34
- data/lib/templates/foundation_nav.html.slim +0 -34
- data/lib/templates/nav.html.erb +0 -22
- data/lib/templates/nav.html.haml +0 -30
- data/lib/templates/nav.html.slim +0 -29
- data/lib/templates/uikit_nav.html.erb +0 -15
- data/lib/templates/uikit_nav.html.haml +0 -28
- data/lib/templates/uikit_nav.html.slim +0 -28
- data/pagy.gemspec +0 -16
data/apps/demo.ru
ADDED
@@ -0,0 +1,449 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# DESCRIPTION
|
4
|
+
# Showcase all the helpers and styles
|
5
|
+
#
|
6
|
+
# DOC
|
7
|
+
# https://ddnexus.github.io/pagy/playground/#3-demo-app
|
8
|
+
#
|
9
|
+
# BIN HELP
|
10
|
+
# bundle exec pagy -h
|
11
|
+
#
|
12
|
+
# DEMO USAGE
|
13
|
+
# bundle exec pagy demo
|
14
|
+
#
|
15
|
+
# DEV USAGE
|
16
|
+
# bundle exec pagy clone demo
|
17
|
+
# bundle exec pagy ./demo.ru
|
18
|
+
#
|
19
|
+
# URL
|
20
|
+
# http://0.0.0.0:8000
|
21
|
+
|
22
|
+
VERSION = '9.4.0'
|
23
|
+
|
24
|
+
# Bundle
|
25
|
+
require 'bundler/inline'
|
26
|
+
require 'bundler'
|
27
|
+
Bundler.configure
|
28
|
+
gemfile(ENV['PAGY_INSTALL_BUNDLE'] == 'true') do
|
29
|
+
source 'https://rubygems.org'
|
30
|
+
gem 'oj'
|
31
|
+
gem 'puma'
|
32
|
+
gem 'rouge'
|
33
|
+
gem 'sinatra'
|
34
|
+
end
|
35
|
+
|
36
|
+
# pagy initializer
|
37
|
+
STYLES = { pagy: { extra: 'pagy', prefix: '', css_anchor: 'pagy-scss' },
|
38
|
+
bootstrap: {},
|
39
|
+
bulma: {},
|
40
|
+
tailwind: { extra: 'pagy', prefix: '', css_anchor: 'pagy-tailwind-css' } }.freeze
|
41
|
+
|
42
|
+
STYLES.each_key do |style|
|
43
|
+
require "pagy/extras/#{STYLES[style][:extra] || style}"
|
44
|
+
end
|
45
|
+
require 'pagy/extras/limit'
|
46
|
+
require 'pagy/extras/trim'
|
47
|
+
Pagy::DEFAULT[:trim_extra] = false # opt-in trim
|
48
|
+
|
49
|
+
# Sinatra setup
|
50
|
+
require 'sinatra/base'
|
51
|
+
|
52
|
+
# Sinatra application
|
53
|
+
class PagyDemo < Sinatra::Base
|
54
|
+
include Pagy::Backend
|
55
|
+
|
56
|
+
get '/' do
|
57
|
+
redirect '/pagy'
|
58
|
+
end
|
59
|
+
|
60
|
+
get '/template' do
|
61
|
+
collection = MockCollection.new
|
62
|
+
@pagy, @records = pagy(collection, trim_extra: params['trim'])
|
63
|
+
|
64
|
+
erb :template, locals: { pagy: @pagy, style: 'pagy' }
|
65
|
+
end
|
66
|
+
|
67
|
+
get('/javascripts/:file') do
|
68
|
+
format = params[:file].split('.').last
|
69
|
+
if format == 'js'
|
70
|
+
content_type 'application/javascript'
|
71
|
+
elsif format == 'map'
|
72
|
+
content_type 'application/json'
|
73
|
+
end
|
74
|
+
send_file Pagy.root.join('javascripts', params[:file])
|
75
|
+
end
|
76
|
+
|
77
|
+
get('/stylesheets/:file') do
|
78
|
+
content_type 'text/css'
|
79
|
+
send_file Pagy.root.join('stylesheets', params[:file])
|
80
|
+
end
|
81
|
+
|
82
|
+
# One route/action per style
|
83
|
+
STYLES.each_key do |style|
|
84
|
+
prefix = STYLES[style][:prefix] || "_#{style}"
|
85
|
+
|
86
|
+
get("/#{style}/?:trim?") do
|
87
|
+
collection = MockCollection.new
|
88
|
+
@pagy, @records = pagy(collection, trim_extra: params['trim'])
|
89
|
+
|
90
|
+
erb :helpers, locals: { style:, prefix: }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
helpers do
|
95
|
+
include Pagy::Frontend
|
96
|
+
|
97
|
+
def style_menu
|
98
|
+
html = +%(<div id="style-menu"> )
|
99
|
+
STYLES.each_key { |style| html << %(<a href="/#{style}">#{style}</a>) }
|
100
|
+
html << %(<a href="/template">template</a>)
|
101
|
+
html << %(</div>)
|
102
|
+
end
|
103
|
+
|
104
|
+
def highlight(html, format: :html)
|
105
|
+
if format == :html
|
106
|
+
html = html.gsub(/>[\s]*</, '><').strip # template single line no spaces around/between tags
|
107
|
+
html = Formatter.new.format(html)
|
108
|
+
end
|
109
|
+
lexer = Rouge::Lexers::ERB.new
|
110
|
+
formatter = Rouge::Formatters::HTMLInline.new('monokai.sublime')
|
111
|
+
summary = { html: 'Served HTML (pretty formatted)', erb: 'ERB Template' }
|
112
|
+
%(<details><summary>#{summary[format]}</summary><pre>\n#{
|
113
|
+
formatter.format(lexer.lex(html))
|
114
|
+
}</pre></details>)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Views
|
119
|
+
template :layout do
|
120
|
+
<<~'ERB'
|
121
|
+
<!DOCTYPE html>
|
122
|
+
<html lang="en">
|
123
|
+
<head>
|
124
|
+
<title>Pagy Demo App</title>
|
125
|
+
<script src="/javascripts/pagy.min.js"></script>
|
126
|
+
<script>
|
127
|
+
window.addEventListener("load", Pagy.init);
|
128
|
+
</script>
|
129
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
130
|
+
<%= erb :"#{style}_head" if defined?(style) %>
|
131
|
+
<style type="text/css">
|
132
|
+
@media screen { html, body {
|
133
|
+
font-size: 1rem;
|
134
|
+
line-height: 1.2s;
|
135
|
+
padding: 0;
|
136
|
+
margin: 0;
|
137
|
+
} }
|
138
|
+
body {
|
139
|
+
background: white !important;
|
140
|
+
margin: 0 !important;
|
141
|
+
font-family: sans-serif !important;
|
142
|
+
}
|
143
|
+
h1, h2 {
|
144
|
+
font-size: 1.8rem !important;
|
145
|
+
font-weight: 600 !important;
|
146
|
+
margin-top: 1rem !important;
|
147
|
+
margin-bottom: 0.7rem !important;
|
148
|
+
line-height: 1.5 !important;
|
149
|
+
color: rgb(90 90 90) !important;
|
150
|
+
}
|
151
|
+
h2 {
|
152
|
+
font-family: monospace;
|
153
|
+
font-size: .9rem !important;
|
154
|
+
margin-top: 1.6rem !important;
|
155
|
+
}
|
156
|
+
summary, .notes {
|
157
|
+
font-size: .8rem;
|
158
|
+
color: gray;
|
159
|
+
margin-top: .6rem;
|
160
|
+
font-style: italic;
|
161
|
+
cursor: pointer;
|
162
|
+
}
|
163
|
+
.notes {
|
164
|
+
font-family: sans-serif;
|
165
|
+
font-weight: normal;
|
166
|
+
}
|
167
|
+
.notes code{
|
168
|
+
background-color: #E8E8E8;
|
169
|
+
padding: 0 0.3rem;
|
170
|
+
font-style: normal;
|
171
|
+
border-radius: 3px;
|
172
|
+
}
|
173
|
+
.description {
|
174
|
+
margin: 1rem 0;
|
175
|
+
}
|
176
|
+
.description a {
|
177
|
+
color: blue;
|
178
|
+
text-decoration: underline;
|
179
|
+
}
|
180
|
+
pre, pre code {
|
181
|
+
display: block;
|
182
|
+
margin-top: .3rem;
|
183
|
+
margin-bottom: 1rem;
|
184
|
+
font-size: .8rem !important;
|
185
|
+
line-height: 1rem !important;
|
186
|
+
color: white;
|
187
|
+
background-color: rgb(30 30 30);
|
188
|
+
padding: 1rem;
|
189
|
+
overflow: auto;
|
190
|
+
}
|
191
|
+
.content {
|
192
|
+
padding: 0 1.5rem 2rem !important;
|
193
|
+
}
|
194
|
+
|
195
|
+
#style-menu {
|
196
|
+
flex;
|
197
|
+
font-family: sans-serif;
|
198
|
+
font-size: 1.1rem;
|
199
|
+
line-height: 1.5rem;
|
200
|
+
white-space: nowrap;
|
201
|
+
color: white;
|
202
|
+
background-color: gray;
|
203
|
+
padding: .2rem 1.5rem;
|
204
|
+
}
|
205
|
+
#style-menu > :not([hidden]) ~ :not([hidden]) {
|
206
|
+
--space-reverse: 0;
|
207
|
+
margin-right: calc(0.5rem * var(--space-reverse));
|
208
|
+
margin-left: calc(0.5rem * calc(1 - var(--space-reverse)));
|
209
|
+
}
|
210
|
+
#style-menu a {
|
211
|
+
color: inherit;
|
212
|
+
text-decoration: none;
|
213
|
+
}
|
214
|
+
/* Quick demo for overriding the element style attribute of certain pagy helpers
|
215
|
+
.pagy input[style] {
|
216
|
+
width: 5rem !important;
|
217
|
+
}
|
218
|
+
*/
|
219
|
+
</style>
|
220
|
+
</head>
|
221
|
+
<body>
|
222
|
+
<!-- each different class used by each style -->
|
223
|
+
<%= style_menu %>
|
224
|
+
<div class="content">
|
225
|
+
<%= yield %>
|
226
|
+
</div>
|
227
|
+
</body>
|
228
|
+
</html>
|
229
|
+
ERB
|
230
|
+
end
|
231
|
+
|
232
|
+
template :pagy_head do
|
233
|
+
<<~ERB
|
234
|
+
<!-- copy and paste the pagy style in order to edit it -->
|
235
|
+
<link rel="stylesheet" href="/stylesheets/pagy.css">
|
236
|
+
ERB
|
237
|
+
end
|
238
|
+
|
239
|
+
template :bootstrap_head do
|
240
|
+
<<~ERB
|
241
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
|
242
|
+
ERB
|
243
|
+
end
|
244
|
+
|
245
|
+
template :bulma_head do
|
246
|
+
<<~ERB
|
247
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.9.4/css/bulma.min.css">
|
248
|
+
ERB
|
249
|
+
end
|
250
|
+
|
251
|
+
template :tailwind_head do
|
252
|
+
<<~ERB
|
253
|
+
<script src="https://cdn.tailwindcss.com?plugins=forms,typography,aspect-ratio"></script>
|
254
|
+
<!-- copy and paste the pagy.tailwind style in order to edit it -->
|
255
|
+
<style type="text/tailwindcss">
|
256
|
+
<%= Pagy.root.join('stylesheets', 'pagy.tailwind.css').read %>
|
257
|
+
</style>
|
258
|
+
ERB
|
259
|
+
end
|
260
|
+
|
261
|
+
template :helpers do
|
262
|
+
<<~'ERB'
|
263
|
+
<h1><%= style %></h1>
|
264
|
+
<% extra = STYLES[style][:extra] || "#{style}" %>
|
265
|
+
<% css_anchor = STYLES[style][:css_anchor] %>
|
266
|
+
|
267
|
+
<p class="description">See the <a href="http://ddnexus.github.io/pagy/docs/extras/<%= extra %>" target="blank"><%= extra %> extra</a>
|
268
|
+
documentation
|
269
|
+
<% if css_anchor %>
|
270
|
+
and the <a href="http://ddnexus.github.io/pagy/docs/api/stylesheets/#<%= css_anchor %>" target="blank"><%= css_anchor.gsub('-', '.') %></a>
|
271
|
+
<% end %>
|
272
|
+
for details</p>
|
273
|
+
|
274
|
+
<h2>Collection</h2>
|
275
|
+
<p id="records">@records: <%= @records.join(',') %></p>
|
276
|
+
|
277
|
+
<h2>pagy<%= prefix %>_nav <span class="notes">Simple nav <code>size: 5</code></span></h2>
|
278
|
+
<%= html = send(:"pagy#{prefix}_nav", @pagy, id: 'simple-nav', aria_label: 'Pages simple-nav', size: 5) %>
|
279
|
+
<%= highlight(html) %>
|
280
|
+
|
281
|
+
<h2>pagy<%= prefix %>_nav <span class="notes">Fast nav <code>size: 7</code></span></h2>
|
282
|
+
<%= html = send(:"pagy#{prefix}_nav", @pagy, id: 'nav', aria_label: 'Pages nav') %>
|
283
|
+
<%= highlight(html) %>
|
284
|
+
|
285
|
+
<h2>pagy<%= prefix %>_nav_js <span class="notes">Fast nav <code>size: 7</code></span></h2>
|
286
|
+
<%= html = send(:"pagy#{prefix}_nav_js", @pagy, id: 'nav-js', aria_label: 'Pages nav_js') %>
|
287
|
+
<%= highlight(html) %>
|
288
|
+
|
289
|
+
<h2>pagy<%= prefix %>_nav_js <span class="notes">Responsive nav <code>steps: {...}</code> (Resize the window to see)</span></h2>
|
290
|
+
<%= html = send(:"pagy#{prefix}_nav_js", @pagy, id: 'nav-js-responsive',
|
291
|
+
aria_label: 'Pages nav_js_responsive',
|
292
|
+
steps: { 0 => 5, 500 => 7, 750 => 9, 1000 => 11 }) %>
|
293
|
+
<%= highlight(html) %>
|
294
|
+
|
295
|
+
<h2>pagy<%= prefix %>_combo_nav_js</h2>
|
296
|
+
<%= html = send(:"pagy#{prefix}_combo_nav_js", @pagy, id: 'combo-nav-js', aria_label: 'Pages combo_nav_js') %>
|
297
|
+
<%= highlight(html) %>
|
298
|
+
|
299
|
+
<h2>pagy_info</h2>
|
300
|
+
<%= html = pagy_info(@pagy, id: 'pagy-info') %>
|
301
|
+
<%= highlight(html) %>
|
302
|
+
|
303
|
+
<% if style.match(/pagy|tailwind/) %>
|
304
|
+
<h2>pagy_limit_selector_js</h2>
|
305
|
+
<%= html = pagy_limit_selector_js(@pagy, id: 'limit-selector-js') %>
|
306
|
+
<%= highlight(html) %>
|
307
|
+
|
308
|
+
<h2>pagy_prev_a / pagy_next_a</h2>
|
309
|
+
<%= html = '<nav class="pagy" id="prev-next" aria-label="Pagy prev-next">' << pagy_prev_a(@pagy) << pagy_next_a(@pagy) << '</nav>' %>
|
310
|
+
<%= highlight(html) %>
|
311
|
+
|
312
|
+
<h2>pagy_prev_link / pagy_next_link <span class="notes">Link not rendered<span></h2>
|
313
|
+
<% html = '<head>' << (pagy_prev_link(@pagy)||'') << (pagy_next_link(@pagy)||'') << '</head>' %>
|
314
|
+
<%= highlight(html) %>
|
315
|
+
<% end %>
|
316
|
+
ERB
|
317
|
+
end
|
318
|
+
|
319
|
+
template :template do
|
320
|
+
<<~ERB
|
321
|
+
<h1>Pagy Template Demo</h1>
|
322
|
+
|
323
|
+
<p class="description">
|
324
|
+
See the <a href="https://ddnexus.github.io/pagy/docs/how-to/#using-your-pagination-templates">
|
325
|
+
Custom Templates</a> documentation.
|
326
|
+
</p>
|
327
|
+
<h2>Collection</h2>
|
328
|
+
<p id="records">@records: <%= @records.join(',') %></p>
|
329
|
+
|
330
|
+
<h2>Rendered ERB template</h2>
|
331
|
+
|
332
|
+
<%# We don't inline the template here, so we can highlight it more easily %>
|
333
|
+
<%= html = ERB.new(TEMPLATE).result(binding) %>
|
334
|
+
<%= highlight(TEMPLATE, format: :erb) %>
|
335
|
+
<%= highlight(html) %>
|
336
|
+
ERB
|
337
|
+
end
|
338
|
+
|
339
|
+
# Easier code highlighting
|
340
|
+
TEMPLATE = <<~ERB
|
341
|
+
<%# IMPORTANT: replace '<%=' with '<%==' if you run this in rails %>
|
342
|
+
|
343
|
+
<%# The a variable below is set to a lambda that generates the a tag %>
|
344
|
+
<%# Usage: a_tag = a.(page_number, text, classes: nil, aria_label: nil) %>
|
345
|
+
<% a = pagy_anchor(pagy) %>
|
346
|
+
<nav class="pagy nav" aria-label="Pages">
|
347
|
+
<%# Previous page link %>
|
348
|
+
<% if pagy.prev %>
|
349
|
+
<%= a.(pagy.prev, '<', aria_label: 'Previous') %>
|
350
|
+
<% else %>
|
351
|
+
<a role="link" aria-disabled="true" aria-label="Previous"><</a>
|
352
|
+
<% end %>
|
353
|
+
<%# Page links (series example: [1, :gap, 7, 8, "9", 10, 11, :gap, 36]) %>
|
354
|
+
<% pagy.series.each do |item| %>
|
355
|
+
<% if item.is_a?(Integer) %>
|
356
|
+
<%= a.(item) %>
|
357
|
+
<% elsif item.is_a?(String) %>
|
358
|
+
<a role="link" aria-disabled="true" aria-current="page" class="current"><%= item %></a>
|
359
|
+
<% elsif item == :gap %>
|
360
|
+
<a role="link" aria-disabled="true" class="gap">…</a>
|
361
|
+
<% end %>
|
362
|
+
<% end %>
|
363
|
+
<%# Next page link %>
|
364
|
+
<% if pagy.next %>
|
365
|
+
<%= a.(pagy.next, '>', aria_label: 'Next') %>
|
366
|
+
<% else %>
|
367
|
+
<a role="link" aria-disabled="true" aria-label="Next"><</a>
|
368
|
+
<% end %>
|
369
|
+
</nav>
|
370
|
+
ERB
|
371
|
+
end
|
372
|
+
|
373
|
+
# Cheap pagy formatter for helpers output
|
374
|
+
class Formatter
|
375
|
+
INDENT = ' '
|
376
|
+
TEXT_SPACE = "\u00B7"
|
377
|
+
TEXT = /^([^<>]+)(.*)/
|
378
|
+
UNPAIRED = /^(<(input|link).*?>)(.*)/
|
379
|
+
PAIRED = %r{^(<(head|nav|div|span|p|a|b|label|ul|li).*?>)(.*?)(</\2>)(.*)}
|
380
|
+
WRAPPER = /<.*?>/
|
381
|
+
DATA_PAGY = /(data-pagy="([^"]+)")/
|
382
|
+
|
383
|
+
def initialize
|
384
|
+
@formatted = +''
|
385
|
+
end
|
386
|
+
|
387
|
+
def format(input, level = 0)
|
388
|
+
process(input, level)
|
389
|
+
@formatted
|
390
|
+
end
|
391
|
+
|
392
|
+
private
|
393
|
+
|
394
|
+
def process(input, level)
|
395
|
+
push = ->(content) { @formatted << ((INDENT * level) << content << "\n") }
|
396
|
+
rest = nil
|
397
|
+
if (match = input.match(TEXT))
|
398
|
+
text, rest = match.captures
|
399
|
+
push.(text.gsub(' ', TEXT_SPACE))
|
400
|
+
elsif (match = input.match(UNPAIRED))
|
401
|
+
tag, _name, rest = match.captures
|
402
|
+
push.(tag)
|
403
|
+
elsif (match = input.match(PAIRED))
|
404
|
+
tag_start, name, block, tag_end, rest = match.captures
|
405
|
+
## Handle incomplete same-tag nesting
|
406
|
+
while block.scan(/<#{name}.*?>/).size > block.scan(tag_end).size
|
407
|
+
more, rest = rest.split(tag_end, 2)
|
408
|
+
block << tag_end << more
|
409
|
+
end
|
410
|
+
if (match = tag_start.match(DATA_PAGY))
|
411
|
+
search, data = match.captures
|
412
|
+
formatted = data.scan(/.{1,76}/).join("\n")
|
413
|
+
replace = %(\n#{INDENT * (level + 1)}data-pagy="#{formatted}")
|
414
|
+
tag_start.sub!(search, replace)
|
415
|
+
end
|
416
|
+
if block.match(WRAPPER)
|
417
|
+
push.(tag_start)
|
418
|
+
process(block, level + 1)
|
419
|
+
push.(tag_end)
|
420
|
+
else
|
421
|
+
push.(tag_start << block << tag_end)
|
422
|
+
end
|
423
|
+
end
|
424
|
+
process(rest, level) if rest
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
# Simple array-based collection that acts as a standard DB collection.
|
429
|
+
class MockCollection < Array
|
430
|
+
def initialize(arr = Array(1..1000))
|
431
|
+
super
|
432
|
+
@collection = clone
|
433
|
+
end
|
434
|
+
|
435
|
+
def offset(value)
|
436
|
+
@collection = self[value..]
|
437
|
+
self
|
438
|
+
end
|
439
|
+
|
440
|
+
def limit(value)
|
441
|
+
@collection[0, value]
|
442
|
+
end
|
443
|
+
|
444
|
+
def count(*)
|
445
|
+
size
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
run PagyDemo
|
data/apps/index.rb
ADDED
data/apps/keyset_ar.ru
ADDED
@@ -0,0 +1,228 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# DESCRIPTION
|
4
|
+
# Showcase the keyset ActiveRecord pagination
|
5
|
+
#
|
6
|
+
# DOC
|
7
|
+
# https://ddnexus.github.io/pagy/playground/#5-keyset-apps
|
8
|
+
#
|
9
|
+
# BIN HELP
|
10
|
+
# bundle exec pagy -h
|
11
|
+
#
|
12
|
+
# DEV USAGE
|
13
|
+
# bundle exec pagy clone keyset_ar
|
14
|
+
# bundle exec pagy ./keyset_ar.ru
|
15
|
+
#
|
16
|
+
# URL
|
17
|
+
# http://0.0.0.0:8000
|
18
|
+
|
19
|
+
VERSION = '9.4.0'
|
20
|
+
|
21
|
+
# Bundle
|
22
|
+
require 'bundler/inline'
|
23
|
+
require 'bundler'
|
24
|
+
Bundler.configure
|
25
|
+
gemfile(ENV['PAGY_INSTALL_BUNDLE'] == 'true') do
|
26
|
+
source 'https://rubygems.org'
|
27
|
+
gem 'activerecord'
|
28
|
+
gem 'puma'
|
29
|
+
gem 'sinatra'
|
30
|
+
gem 'sqlite3'
|
31
|
+
end
|
32
|
+
|
33
|
+
# Pagy initializer
|
34
|
+
require 'pagy/extras/limit'
|
35
|
+
require 'pagy/extras/keyset'
|
36
|
+
require 'pagy/extras/pagy'
|
37
|
+
Pagy::DEFAULT[:limit] = 10
|
38
|
+
Pagy::DEFAULT.freeze
|
39
|
+
|
40
|
+
# Sinatra setup
|
41
|
+
require 'sinatra/base'
|
42
|
+
# Sinatra application
|
43
|
+
class PagyKeyset < Sinatra::Base
|
44
|
+
include Pagy::Backend
|
45
|
+
|
46
|
+
# Root route/action
|
47
|
+
get '/' do
|
48
|
+
Time.zone = 'UTC'
|
49
|
+
|
50
|
+
@order = { animal: :asc, name: :asc, birthdate: :desc, id: :asc }
|
51
|
+
@pagy, @pets = pagy_keyset(Pet.order(@order))
|
52
|
+
erb :main
|
53
|
+
end
|
54
|
+
|
55
|
+
helpers do
|
56
|
+
include Pagy::Frontend
|
57
|
+
|
58
|
+
def order_symbol(dir)
|
59
|
+
{ asc: '↗', desc: '↘' }[dir]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
# Views
|
64
|
+
template :layout do
|
65
|
+
<<~ERB
|
66
|
+
<!DOCTYPE html>
|
67
|
+
<html lang="en">
|
68
|
+
<html>
|
69
|
+
<head>
|
70
|
+
<title>Pagy Keyset App</title>
|
71
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
72
|
+
<style type="text/css">
|
73
|
+
@media screen { html, body {
|
74
|
+
font-size: 1rem;
|
75
|
+
line-height: 1.2s;
|
76
|
+
padding: 0;
|
77
|
+
margin: 0;
|
78
|
+
} }
|
79
|
+
body {
|
80
|
+
background: white !important;
|
81
|
+
margin: 0 !important;
|
82
|
+
font-family: sans-serif !important;
|
83
|
+
}
|
84
|
+
.content {
|
85
|
+
padding: 1rem 1.5rem 2rem !important;
|
86
|
+
}
|
87
|
+
|
88
|
+
<%= Pagy.root.join('stylesheets', 'pagy.css').read %>
|
89
|
+
</style>
|
90
|
+
</head>
|
91
|
+
<body>
|
92
|
+
<%= yield %>
|
93
|
+
</body>
|
94
|
+
</html>
|
95
|
+
ERB
|
96
|
+
end
|
97
|
+
|
98
|
+
template :main do
|
99
|
+
<<~ERB
|
100
|
+
<div class="content">
|
101
|
+
<h1>Pagy Keyset App</h1>
|
102
|
+
<p>Self-contained, standalone app usable to easily reproduce any keyset related pagy issue with ActiveRecord sets.</p>
|
103
|
+
<p>Please, report the following versions in any new issue.</p>
|
104
|
+
<h2>Versions</h2>
|
105
|
+
<ul>
|
106
|
+
<li>Ruby: <%= RUBY_VERSION %></li>
|
107
|
+
<li>Rack: <%= Rack::RELEASE %></li>
|
108
|
+
<li>Sinatra: <%= Sinatra::VERSION %></li>
|
109
|
+
<li>Pagy: <%= Pagy::VERSION %></li>
|
110
|
+
</ul>
|
111
|
+
|
112
|
+
<h3>Collection</h3>
|
113
|
+
<div id="records" class="collection">
|
114
|
+
<table border="1" cellspacing="0" cellpadding="3">
|
115
|
+
<tr>
|
116
|
+
<th>animal <%= order_symbol(@order[:animal]) %></th>
|
117
|
+
<th>name <%= order_symbol(@order[:name]) %></th>
|
118
|
+
<th>birthdate <%= order_symbol(@order[:birthdate]) %></th>
|
119
|
+
<th>id <%= order_symbol(@order[:id]) %></th>
|
120
|
+
</tr>
|
121
|
+
<% @pets.each do |pet| %>
|
122
|
+
<tr>
|
123
|
+
<td><%= pet.animal %></td>
|
124
|
+
<td><%= pet.name %></td>
|
125
|
+
<td><%= pet.birthdate %></td>
|
126
|
+
<td><%= pet.id %></td>
|
127
|
+
</tr>
|
128
|
+
<% end %>
|
129
|
+
</table>
|
130
|
+
</div>
|
131
|
+
<p>
|
132
|
+
<nav class="pagy" id="next" aria-label="Pagy next">
|
133
|
+
<%= pagy_next_a(@pagy, text: 'Next page >') %>
|
134
|
+
</nav>
|
135
|
+
</div>
|
136
|
+
ERB
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# ActiveRecord setup
|
141
|
+
require 'active_record'
|
142
|
+
|
143
|
+
# Match the microsecods with the strings stored into the time columns of SQLite
|
144
|
+
# ActiveSupport::JSON::Encoding.time_precision = 6
|
145
|
+
|
146
|
+
# Log
|
147
|
+
output = ENV['APP_ENV'].equal?('showcase') ? IO::NULL : $stdout
|
148
|
+
ActiveRecord::Base.logger = Logger.new(output)
|
149
|
+
# SQLite DB files
|
150
|
+
dir = ENV['APP_ENV'].equal?('development') ? '.' : Dir.pwd # app dir in dev or pwd otherwise
|
151
|
+
abort "ERROR: Cannot create DB files: the directory #{dir.inspect} is not writable." \
|
152
|
+
unless File.writable?(dir)
|
153
|
+
# Connection
|
154
|
+
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: "#{dir}/tmp/pagy-keyset-ar.sqlite3")
|
155
|
+
# Schema
|
156
|
+
ActiveRecord::Schema.define do
|
157
|
+
create_table :pets, force: true do |t|
|
158
|
+
t.string :animal
|
159
|
+
t.string :name
|
160
|
+
t.date :birthdate
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Models
|
165
|
+
class Pet < ActiveRecord::Base; end
|
166
|
+
|
167
|
+
data = <<~DATA
|
168
|
+
Luna | dog | 2018-03-10
|
169
|
+
Coco | cat | 2019-05-15
|
170
|
+
Dodo | dog | 2020-06-25
|
171
|
+
Wiki | bird | 2018-03-12
|
172
|
+
Baby | rabbit | 2020-01-13
|
173
|
+
Neki | horse | 2021-07-20
|
174
|
+
Tino | donkey | 2019-06-18
|
175
|
+
Plot | cat | 2022-09-21
|
176
|
+
Riki | cat | 2018-09-14
|
177
|
+
Susi | horse | 2018-10-26
|
178
|
+
Coco | pig | 2020-08-29
|
179
|
+
Momo | bird | 2023-08-25
|
180
|
+
Lili | cat | 2021-07-22
|
181
|
+
Beli | pig | 2020-07-26
|
182
|
+
Rocky | bird | 2022-08-19
|
183
|
+
Vyvy | dog | 2018-05-16
|
184
|
+
Susi | horse | 2024-01-25
|
185
|
+
Ella | cat | 2020-02-20
|
186
|
+
Rocky | dog | 2019-09-19
|
187
|
+
Juni | rabbit | 2020-08-24
|
188
|
+
Coco | bird | 2021-03-17
|
189
|
+
Susi | dog | 2021-07-28
|
190
|
+
Luna | horse | 2023-05-14
|
191
|
+
Gigi | pig | 2022-05-19
|
192
|
+
Coco | cat | 2020-02-20
|
193
|
+
Nino | donkey | 2019-06-17
|
194
|
+
Luna | cat | 2022-02-09
|
195
|
+
Popi | dog | 2020-09-26
|
196
|
+
Lili | pig | 2022-06-18
|
197
|
+
Mina | horse | 2021-04-21
|
198
|
+
Susi | rabbit | 2023-05-18
|
199
|
+
Toni | donkey | 2018-06-22
|
200
|
+
Rocky | horse | 2019-09-28
|
201
|
+
Lili | cat | 2019-03-18
|
202
|
+
Roby | cat | 2022-06-19
|
203
|
+
Anto | horse | 2022-08-18
|
204
|
+
Susi | pig | 2021-04-21
|
205
|
+
Boly | bird | 2020-03-29
|
206
|
+
Sky | cat | 2023-07-19
|
207
|
+
Lili | dog | 2020-01-28
|
208
|
+
Fami | snake | 2023-04-27
|
209
|
+
Lopi | pig | 2019-06-19
|
210
|
+
Rocky | snake | 2022-03-13
|
211
|
+
Denis | dog | 2022-06-19
|
212
|
+
Maca | cat | 2022-06-19
|
213
|
+
Luna | dog | 2022-08-15
|
214
|
+
Jeme | horse | 2019-08-08
|
215
|
+
Sary | bird | 2023-04-29
|
216
|
+
Rocky | bird | 2023-05-14
|
217
|
+
Coco | dog | 2023-05-27
|
218
|
+
DATA
|
219
|
+
|
220
|
+
# DB seed
|
221
|
+
pets = []
|
222
|
+
data.each_line(chomp: true) do |pet|
|
223
|
+
name, animal, birthdate = pet.split('|').map(&:strip)
|
224
|
+
pets << { name:, animal:, birthdate: }
|
225
|
+
end
|
226
|
+
Pet.insert_all(pets)
|
227
|
+
|
228
|
+
run PagyKeyset
|