ice_cube-select 4.0.3 → 4.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 230f1e378cf7b69122298e0fc8920d9bdb93ca122c8c7f94fbb0c28e7396c0f5
4
- data.tar.gz: 99b34354b2d5b2d48b8d6d83a5480e44be96882986c01b826942c04d27a51ce1
3
+ metadata.gz: 4e5257b727c7e07e196cae557934c60074fac624aed47c72de58eb157d958756
4
+ data.tar.gz: eaf00763e8e77217e1bb43cdc1fcf6b88694c398d019ed3c80809e13a7876d6f
5
5
  SHA512:
6
- metadata.gz: f32828f0644ab2fac30f43c51b02308e968a475fe83e4be38af357fa4112070e8598e2eb0082c8d8f7a4cfd709f190bf7ecd35c8f51d2feb900f7b77b2c240e1
7
- data.tar.gz: 5f39310d8cb4149b78802dd4e49f964452015fb6b7afbe6b76c9227157804e91a7acdeb70b18ccb8bb2328d469add0bf9190868af0f242db9687cd4b68f9f8ce
6
+ metadata.gz: f86927be2f0d0a75af929649b2f533c4fcabde97e11f3744f0fb08949ec4a3c820c4f8787350c4033bad5f237e493e5e772fbb1c15d1c08219b8b4f2139bcbc4
7
+ data.tar.gz: c1a409c8c31133b95762d916b005d71bc4f862e6cf06753033fac7f794e4df16fd6a4e3486c27b9de433dd6c924e58144aa8ba7d73dcb9b07c92368420c89df3
data/README.md CHANGED
@@ -22,18 +22,15 @@ Add the gem to your Gemfile:
22
22
  gem 'ice_cube-select'
23
23
  ```
24
24
 
25
- Then install the assets:
26
-
27
25
  ```bash
28
26
  bundle install
29
- rails generate ice_cube_select:install
30
27
  ```
31
28
 
32
- This copies the CSS and JavaScript files to your `app/assets` directory for use with Propshaft (Rails 8 default asset pipeline).
33
-
34
29
  ### Assets
35
30
 
36
- Include the stylesheet and JavaScript in your layout:
31
+ The gem ships its CSS and JavaScript as engine assets. Under Propshaft (the
32
+ Rails 8 default) and Sprockets, the engine's `app/assets` directories are already
33
+ on the asset load path, so you only need to reference the files in your layout:
37
34
 
38
35
  ```erb
39
36
  <%= stylesheet_link_tag "ice_cube_select" %>
@@ -41,6 +38,22 @@ Include the stylesheet and JavaScript in your layout:
41
38
  <%= javascript_include_tag "ice_cube_select_dialog" %>
42
39
  ```
43
40
 
41
+ No install or copy step is required — updating the gem updates the assets.
42
+
43
+ #### Customizing the assets (optional)
44
+
45
+ If you want to fork the CSS or JavaScript to tweak it locally, copy the files
46
+ into your app with:
47
+
48
+ ```bash
49
+ rails generate ice_cube_select:install
50
+ ```
51
+
52
+ This writes copies to your `app/assets` directory, where they take precedence
53
+ over the gem's versions. Note that these copies will **not** update when you
54
+ upgrade the gem — you own them from then on, so re-run the generator (or diff
55
+ against the gem) after upgrading.
56
+
44
57
  ### Form Helper
45
58
 
46
59
  In the form view call the helper:
@@ -143,6 +143,9 @@
143
143
  weekly: 'Weekly',
144
144
  monthly: 'Monthly',
145
145
  yearly: 'Yearly',
146
+ anchored_monthly: 'Anchored monthly',
147
+ after_the: 'After the',
148
+ of_the_month: 'of the month, on these days after it:',
146
149
  every: 'Every',
147
150
  days: 'day(s)',
148
151
  weeks_on: 'week(s) on',
@@ -155,6 +158,7 @@
155
158
  summary: 'Summary',
156
159
  first_day_of_week: 0,
157
160
  days_first_letter: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
161
+ day_names: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
158
162
  order: ['1st', '2nd', '3rd', '4th', '5th', 'Last'],
159
163
  show_week: [true, true, true, true, false, false]
160
164
  };
@@ -134,7 +134,10 @@
134
134
  if (this.currentRule.hash && this.currentRule.hash.rule_type) {
135
135
  const ruleType = this.currentRule.hash.rule_type;
136
136
 
137
- if (ruleType.includes('Weekly')) {
137
+ if (ruleType === 'IceCube::AnchoredMonthlyRule') {
138
+ this.freqSelect.selectedIndex = 4;
139
+ this.initAnchoredOptions();
140
+ } else if (ruleType.includes('Weekly')) {
138
141
  this.freqSelect.selectedIndex = 1;
139
142
  this.initWeeklyOptions();
140
143
  } else if (ruleType.includes('Monthly')) {
@@ -244,6 +247,59 @@
244
247
  section.style.display = 'block';
245
248
  }
246
249
 
250
+ initAnchoredOptions() {
251
+ const section = this.content.querySelector('.anchored_options');
252
+
253
+ this.currentRule.hash = this.currentRule.hash || {};
254
+ this.currentRule.hash.anchor_ordinal = this.currentRule.hash.anchor_ordinal ?? 1;
255
+ this.currentRule.hash.anchor_weekday = this.currentRule.hash.anchor_weekday ?? 6;
256
+ if (!this.currentRule.hash.day_offsets || !this.currentRule.hash.day_offsets.length) {
257
+ this.currentRule.hash.day_offsets = [1, 3, 5, 8];
258
+ }
259
+
260
+ const ordinalSelect = section.querySelector('.anchored_ordinal');
261
+ ordinalSelect.value = this.currentRule.hash.anchor_ordinal;
262
+
263
+ const weekdaySelect = section.querySelector('.anchored_weekday');
264
+ if (!weekdaySelect.options.length) {
265
+ const dayNames = IceCubeSelect.texts.day_names || [0, 1, 2, 3, 4, 5, 6].map(num => this.fullStringWeekday(num));
266
+ dayNames.forEach((name, index) => {
267
+ const option = document.createElement('option');
268
+ option.value = index;
269
+ option.textContent = name;
270
+ weekdaySelect.appendChild(option);
271
+ });
272
+ }
273
+ weekdaySelect.value = this.currentRule.hash.anchor_weekday;
274
+
275
+ const offsetsGrid = section.querySelector('.anchored_offsets');
276
+ offsetsGrid.innerHTML = '';
277
+ const selectedOffsets = new Set(this.currentRule.hash.day_offsets);
278
+
279
+ for (let offset = 1; offset <= 14; offset++) {
280
+ const offsetLink = document.createElement('a');
281
+ offsetLink.href = '';
282
+ offsetLink.textContent = offset;
283
+ offsetLink.dataset.offset = offset;
284
+ offsetLink.setAttribute('aria-label', this.ordinalSuffixOf(offset) + ' day after anchor');
285
+
286
+ if (selectedOffsets.has(offset)) offsetLink.classList.add('selected');
287
+
288
+ offsetsGrid.appendChild(offsetLink);
289
+ }
290
+
291
+ ordinalSelect.addEventListener('change', () => this.anchoredMetaChanged());
292
+ weekdaySelect.addEventListener('change', () => this.anchoredMetaChanged());
293
+
294
+ offsetsGrid.addEventListener('click', (e) => {
295
+ if (e.target.tagName === 'A') {
296
+ this.anchoredOffsetChanged(e);
297
+ }
298
+ });
299
+
300
+ section.style.display = 'block';
301
+ }
302
+
247
303
  summaryInit() {
248
304
  this.summary = this.outerHolder.querySelector('.ice_cube_select_summary');
249
305
  this.summaryUpdate();
@@ -432,6 +488,11 @@
432
488
  this.currentRule.str = IceCubeSelect.texts.yearly;
433
489
  this.initYearlyOptions();
434
490
  break;
491
+ case 'Anchored':
492
+ this.currentRule.hash.rule_type = 'IceCube::AnchoredMonthlyRule';
493
+ this.currentRule.str = IceCubeSelect.texts.anchored_monthly || 'Anchored monthly';
494
+ this.initAnchoredOptions();
495
+ break;
435
496
  default:
436
497
  this.currentRule.hash.rule_type = 'IceCube::DailyRule';
437
498
  this.currentRule.str = IceCubeSelect.texts.daily;
@@ -509,6 +570,33 @@
509
570
  this.summaryUpdate();
510
571
  }
511
572
 
573
+ anchoredMetaChanged() {
574
+ const section = this.content.querySelector('.anchored_options');
575
+
576
+ this.currentRule.str = null;
577
+ this.currentRule.hash = this.currentRule.hash || {};
578
+ this.currentRule.hash.anchor_ordinal = parseInt(section.querySelector('.anchored_ordinal').value, 10);
579
+ this.currentRule.hash.anchor_weekday = parseInt(section.querySelector('.anchored_weekday').value, 10);
580
+
581
+ this.summaryUpdate();
582
+ }
583
+
584
+ anchoredOffsetChanged(event) {
585
+ event.preventDefault();
586
+ event.target.classList.toggle('selected');
587
+
588
+ this.currentRule.str = null;
589
+ this.currentRule.hash = this.currentRule.hash || {};
590
+
591
+ const section = this.content.querySelector('.anchored_options');
592
+ const selectedOffsets = section.querySelectorAll('.anchored_offsets a.selected');
593
+ this.currentRule.hash.day_offsets = Array.from(selectedOffsets)
594
+ .map(el => parseInt(el.dataset.offset, 10))
595
+ .sort((a, b) => a - b);
596
+
597
+ this.summaryUpdate();
598
+ }
599
+
512
600
  template() {
513
601
  const texts = IceCubeSelect.texts;
514
602
  const firstDay = texts.first_day_of_week;
@@ -535,6 +623,7 @@
535
623
  <option value="Weekly">${texts.weekly}</option>
536
624
  <option value="Monthly">${texts.monthly}</option>
537
625
  <option value="Yearly">${texts.yearly}</option>
626
+ <option value="Anchored">${texts.anchored_monthly || 'Anchored monthly'}</option>
538
627
  </select>
539
628
  </p>
540
629
 
@@ -584,6 +673,22 @@
584
673
  </p>
585
674
  </div>
586
675
 
676
+ <div class="anchored_options freq_option_section">
677
+ <p>
678
+ <label>${texts.after_the || 'After the'}
679
+ <select class="anchored_ordinal">
680
+ <option value="1">1st</option>
681
+ <option value="2">2nd</option>
682
+ <option value="3">3rd</option>
683
+ <option value="4">4th</option>
684
+ </select>
685
+ <select class="anchored_weekday"></select>
686
+ ${texts.of_the_month || 'of the month, on these days after it:'}
687
+ </label>
688
+ </p>
689
+ <div class="anchored_offsets"></div>
690
+ </div>
691
+
587
692
  <p class="ice_cube_select_summary">
588
693
  <span></span>
589
694
  </p>
@@ -6,3 +6,6 @@ en:
6
6
  new_custom_schedule: "New custom schedule..."
7
7
  custom_schedule: "Custom schedule..."
8
8
  or: or
9
+ anchored_monthly: "Anchored monthly"
10
+ after_the: "After the"
11
+ of_the_month: "of the month, on these days after it:"
@@ -0,0 +1,436 @@
1
+ PATH
2
+ remote: ..
3
+ specs:
4
+ ice_cube-select (4.0.4)
5
+ ice_cube (>= 0.17)
6
+ rails (>= 8.0)
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ action_text-trix (2.1.19)
12
+ railties
13
+ actioncable (8.1.3)
14
+ actionpack (= 8.1.3)
15
+ activesupport (= 8.1.3)
16
+ nio4r (~> 2.0)
17
+ websocket-driver (>= 0.6.1)
18
+ zeitwerk (~> 2.6)
19
+ actionmailbox (8.1.3)
20
+ actionpack (= 8.1.3)
21
+ activejob (= 8.1.3)
22
+ activerecord (= 8.1.3)
23
+ activestorage (= 8.1.3)
24
+ activesupport (= 8.1.3)
25
+ mail (>= 2.8.0)
26
+ actionmailer (8.1.3)
27
+ actionpack (= 8.1.3)
28
+ actionview (= 8.1.3)
29
+ activejob (= 8.1.3)
30
+ activesupport (= 8.1.3)
31
+ mail (>= 2.8.0)
32
+ rails-dom-testing (~> 2.2)
33
+ actionpack (8.1.3)
34
+ actionview (= 8.1.3)
35
+ activesupport (= 8.1.3)
36
+ nokogiri (>= 1.8.5)
37
+ rack (>= 2.2.4)
38
+ rack-session (>= 1.0.1)
39
+ rack-test (>= 0.6.3)
40
+ rails-dom-testing (~> 2.2)
41
+ rails-html-sanitizer (~> 1.6)
42
+ useragent (~> 0.16)
43
+ actiontext (8.1.3)
44
+ action_text-trix (~> 2.1.15)
45
+ actionpack (= 8.1.3)
46
+ activerecord (= 8.1.3)
47
+ activestorage (= 8.1.3)
48
+ activesupport (= 8.1.3)
49
+ globalid (>= 0.6.0)
50
+ nokogiri (>= 1.8.5)
51
+ actionview (8.1.3)
52
+ activesupport (= 8.1.3)
53
+ builder (~> 3.1)
54
+ erubi (~> 1.11)
55
+ rails-dom-testing (~> 2.2)
56
+ rails-html-sanitizer (~> 1.6)
57
+ activejob (8.1.3)
58
+ activesupport (= 8.1.3)
59
+ globalid (>= 0.3.6)
60
+ activemodel (8.1.3)
61
+ activesupport (= 8.1.3)
62
+ activerecord (8.1.3)
63
+ activemodel (= 8.1.3)
64
+ activesupport (= 8.1.3)
65
+ timeout (>= 0.4.0)
66
+ activestorage (8.1.3)
67
+ actionpack (= 8.1.3)
68
+ activejob (= 8.1.3)
69
+ activerecord (= 8.1.3)
70
+ activesupport (= 8.1.3)
71
+ marcel (~> 1.0)
72
+ activesupport (8.1.3)
73
+ base64
74
+ bigdecimal
75
+ concurrent-ruby (~> 1.0, >= 1.3.1)
76
+ connection_pool (>= 2.2.5)
77
+ drb
78
+ i18n (>= 1.6, < 2)
79
+ json
80
+ logger (>= 1.4.2)
81
+ minitest (>= 5.1)
82
+ securerandom (>= 0.3)
83
+ tzinfo (~> 2.0, >= 2.0.5)
84
+ uri (>= 0.13.1)
85
+ addressable (2.9.0)
86
+ public_suffix (>= 2.0.2, < 8.0)
87
+ ast (2.4.3)
88
+ base64 (0.3.0)
89
+ bigdecimal (4.1.2)
90
+ builder (3.3.0)
91
+ capybara (3.40.0)
92
+ addressable
93
+ matrix
94
+ mini_mime (>= 0.1.3)
95
+ nokogiri (~> 1.11)
96
+ rack (>= 1.6.0)
97
+ rack-test (>= 0.6.3)
98
+ regexp_parser (>= 1.5, < 3.0)
99
+ xpath (~> 3.2)
100
+ capybara-playwright-driver (0.5.9)
101
+ addressable
102
+ capybara
103
+ playwright-ruby-client (>= 1.16.0)
104
+ concurrent-ruby (1.3.7)
105
+ connection_pool (3.0.2)
106
+ crass (1.0.7)
107
+ date (3.5.1)
108
+ diff-lcs (1.6.2)
109
+ docile (1.4.1)
110
+ drb (2.2.3)
111
+ erb (6.0.4)
112
+ erubi (1.13.1)
113
+ globalid (1.4.0)
114
+ activesupport (>= 6.1)
115
+ i18n (1.15.2)
116
+ concurrent-ruby (~> 1.0)
117
+ ice_cube (0.17.0)
118
+ io-console (0.8.2)
119
+ irb (1.18.0)
120
+ pp (>= 0.6.0)
121
+ prism (>= 1.3.0)
122
+ rdoc (>= 4.0.0)
123
+ reline (>= 0.4.2)
124
+ json (2.20.0)
125
+ language_server-protocol (3.17.0.6)
126
+ lint_roller (1.1.0)
127
+ logger (1.7.0)
128
+ loofah (2.25.1)
129
+ crass (~> 1.0.2)
130
+ nokogiri (>= 1.12.0)
131
+ mail (2.9.1)
132
+ logger
133
+ mini_mime (>= 0.1.1)
134
+ net-imap
135
+ net-pop
136
+ net-smtp
137
+ marcel (1.2.1)
138
+ matrix (0.4.3)
139
+ mime-types (3.7.0)
140
+ logger
141
+ mime-types-data (~> 3.2025, >= 3.2025.0507)
142
+ mime-types-data (3.2026.0414)
143
+ mini_mime (1.1.5)
144
+ minitest (6.0.6)
145
+ drb (~> 2.0)
146
+ prism (~> 1.5)
147
+ net-imap (0.6.4.1)
148
+ date
149
+ net-protocol
150
+ net-pop (0.1.2)
151
+ net-protocol
152
+ net-protocol (0.2.2)
153
+ timeout
154
+ net-smtp (0.5.1)
155
+ net-protocol
156
+ nio4r (2.7.5)
157
+ nokogiri (1.19.4-arm64-darwin)
158
+ racc (~> 1.4)
159
+ parallel (2.1.0)
160
+ parser (3.3.11.1)
161
+ ast (~> 2.4.1)
162
+ racc
163
+ playwright-ruby-client (1.60.0)
164
+ base64
165
+ concurrent-ruby (>= 1.1.6)
166
+ mime-types (>= 3.0)
167
+ pp (0.6.4)
168
+ prettyprint
169
+ prettyprint (0.2.0)
170
+ prism (1.9.0)
171
+ propshaft (1.3.2)
172
+ actionpack (>= 7.0.0)
173
+ activesupport (>= 7.0.0)
174
+ rack
175
+ public_suffix (7.0.5)
176
+ puma (8.0.2)
177
+ nio4r (~> 2.0)
178
+ racc (1.8.1)
179
+ rack (3.2.6)
180
+ rack-session (2.1.2)
181
+ base64 (>= 0.1.0)
182
+ rack (>= 3.0.0)
183
+ rack-test (2.2.0)
184
+ rack (>= 1.3)
185
+ rackup (2.3.1)
186
+ rack (>= 3)
187
+ rails (8.1.3)
188
+ actioncable (= 8.1.3)
189
+ actionmailbox (= 8.1.3)
190
+ actionmailer (= 8.1.3)
191
+ actionpack (= 8.1.3)
192
+ actiontext (= 8.1.3)
193
+ actionview (= 8.1.3)
194
+ activejob (= 8.1.3)
195
+ activemodel (= 8.1.3)
196
+ activerecord (= 8.1.3)
197
+ activestorage (= 8.1.3)
198
+ activesupport (= 8.1.3)
199
+ bundler (>= 1.15.0)
200
+ railties (= 8.1.3)
201
+ rails-dom-testing (2.3.0)
202
+ activesupport (>= 5.0.0)
203
+ minitest
204
+ nokogiri (>= 1.6)
205
+ rails-html-sanitizer (1.7.0)
206
+ loofah (~> 2.25)
207
+ nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
208
+ railties (8.1.3)
209
+ actionpack (= 8.1.3)
210
+ activesupport (= 8.1.3)
211
+ irb (~> 1.13)
212
+ rackup (>= 1.0.0)
213
+ rake (>= 12.2)
214
+ thor (~> 1.0, >= 1.2.2)
215
+ tsort (>= 0.2)
216
+ zeitwerk (~> 2.6)
217
+ rainbow (3.1.1)
218
+ rake (13.4.2)
219
+ rbs (4.0.3)
220
+ logger
221
+ prism (>= 1.6.0)
222
+ tsort
223
+ rdoc (8.0.0)
224
+ erb
225
+ prism (>= 1.6.0)
226
+ rbs (>= 4.0.0)
227
+ tsort
228
+ regexp_parser (2.12.0)
229
+ reissue (0.4.22)
230
+ rake
231
+ reline (0.6.3)
232
+ io-console (~> 0.5)
233
+ rspec (3.13.2)
234
+ rspec-core (~> 3.13.0)
235
+ rspec-expectations (~> 3.13.0)
236
+ rspec-mocks (~> 3.13.0)
237
+ rspec-core (3.13.6)
238
+ rspec-support (~> 3.13.0)
239
+ rspec-expectations (3.13.5)
240
+ diff-lcs (>= 1.2.0, < 2.0)
241
+ rspec-support (~> 3.13.0)
242
+ rspec-mocks (3.13.8)
243
+ diff-lcs (>= 1.2.0, < 2.0)
244
+ rspec-support (~> 3.13.0)
245
+ rspec-rails (7.1.1)
246
+ actionpack (>= 7.0)
247
+ activesupport (>= 7.0)
248
+ railties (>= 7.0)
249
+ rspec-core (~> 3.13)
250
+ rspec-expectations (~> 3.13)
251
+ rspec-mocks (~> 3.13)
252
+ rspec-support (~> 3.13)
253
+ rspec-support (3.13.7)
254
+ rubocop (1.87.0)
255
+ json (~> 2.3)
256
+ language_server-protocol (~> 3.17.0.2)
257
+ lint_roller (~> 1.1.0)
258
+ parallel (>= 1.10)
259
+ parser (>= 3.3.0.2)
260
+ rainbow (>= 2.2.2, < 4.0)
261
+ regexp_parser (>= 2.9.3, < 3.0)
262
+ rubocop-ast (>= 1.49.0, < 2.0)
263
+ ruby-progressbar (~> 1.7)
264
+ unicode-display_width (>= 2.4.0, < 4.0)
265
+ rubocop-ast (1.50.0)
266
+ parser (>= 3.3.7.2)
267
+ prism (~> 1.7)
268
+ rubocop-performance (1.26.1)
269
+ lint_roller (~> 1.1)
270
+ rubocop (>= 1.75.0, < 2.0)
271
+ rubocop-ast (>= 1.47.1, < 2.0)
272
+ ruby-progressbar (1.13.0)
273
+ securerandom (0.4.1)
274
+ simplecov (0.22.0)
275
+ docile (~> 1.1)
276
+ simplecov-html (~> 0.11)
277
+ simplecov_json_formatter (~> 0.1)
278
+ simplecov-html (0.13.2)
279
+ simplecov_json_formatter (0.1.4)
280
+ standard (1.55.0)
281
+ language_server-protocol (~> 3.17.0.2)
282
+ lint_roller (~> 1.0)
283
+ rubocop (~> 1.87.0)
284
+ standard-custom (~> 1.0.0)
285
+ standard-performance (~> 1.8)
286
+ standard-custom (1.0.2)
287
+ lint_roller (~> 1.0)
288
+ rubocop (~> 1.50)
289
+ standard-performance (1.9.0)
290
+ lint_roller (~> 1.1)
291
+ rubocop-performance (~> 1.26.0)
292
+ thor (1.5.0)
293
+ timeout (0.6.1)
294
+ tsort (0.2.0)
295
+ tzinfo (2.0.6)
296
+ concurrent-ruby (~> 1.0)
297
+ unicode-display_width (3.2.0)
298
+ unicode-emoji (~> 4.1)
299
+ unicode-emoji (4.2.0)
300
+ uri (1.1.1)
301
+ useragent (0.16.11)
302
+ websocket-driver (0.8.2)
303
+ base64
304
+ websocket-extensions (>= 0.1.0)
305
+ websocket-extensions (0.1.5)
306
+ xpath (3.2.0)
307
+ nokogiri (~> 1.8)
308
+ zeitwerk (2.8.2)
309
+
310
+ PLATFORMS
311
+ arm64-darwin
312
+
313
+ DEPENDENCIES
314
+ capybara
315
+ capybara-playwright-driver
316
+ ice_cube-select!
317
+ propshaft
318
+ puma
319
+ rails (~> 8.0)
320
+ rake
321
+ reissue
322
+ rspec (>= 2.14, < 4)
323
+ rspec-rails (>= 2.14, < 8)
324
+ simplecov
325
+ standard
326
+
327
+ CHECKSUMS
328
+ action_text-trix (2.1.19)
329
+ actioncable (8.1.3)
330
+ actionmailbox (8.1.3)
331
+ actionmailer (8.1.3)
332
+ actionpack (8.1.3)
333
+ actiontext (8.1.3)
334
+ actionview (8.1.3)
335
+ activejob (8.1.3)
336
+ activemodel (8.1.3)
337
+ activerecord (8.1.3)
338
+ activestorage (8.1.3)
339
+ activesupport (8.1.3)
340
+ addressable (2.9.0)
341
+ ast (2.4.3)
342
+ base64 (0.3.0)
343
+ bigdecimal (4.1.2)
344
+ builder (3.3.0)
345
+ capybara (3.40.0)
346
+ capybara-playwright-driver (0.5.9)
347
+ concurrent-ruby (1.3.7)
348
+ connection_pool (3.0.2)
349
+ crass (1.0.7)
350
+ date (3.5.1)
351
+ diff-lcs (1.6.2)
352
+ docile (1.4.1)
353
+ drb (2.2.3)
354
+ erb (6.0.4)
355
+ erubi (1.13.1)
356
+ globalid (1.4.0)
357
+ i18n (1.15.2)
358
+ ice_cube (0.17.0)
359
+ ice_cube-select (4.0.4)
360
+ io-console (0.8.2)
361
+ irb (1.18.0)
362
+ json (2.20.0)
363
+ language_server-protocol (3.17.0.6)
364
+ lint_roller (1.1.0)
365
+ logger (1.7.0)
366
+ loofah (2.25.1)
367
+ mail (2.9.1)
368
+ marcel (1.2.1)
369
+ matrix (0.4.3)
370
+ mime-types (3.7.0)
371
+ mime-types-data (3.2026.0414)
372
+ mini_mime (1.1.5)
373
+ minitest (6.0.6)
374
+ net-imap (0.6.4.1)
375
+ net-pop (0.1.2)
376
+ net-protocol (0.2.2)
377
+ net-smtp (0.5.1)
378
+ nio4r (2.7.5)
379
+ nokogiri (1.19.4-arm64-darwin)
380
+ parallel (2.1.0)
381
+ parser (3.3.11.1)
382
+ playwright-ruby-client (1.60.0)
383
+ pp (0.6.4)
384
+ prettyprint (0.2.0)
385
+ prism (1.9.0)
386
+ propshaft (1.3.2)
387
+ public_suffix (7.0.5)
388
+ puma (8.0.2)
389
+ racc (1.8.1)
390
+ rack (3.2.6)
391
+ rack-session (2.1.2)
392
+ rack-test (2.2.0)
393
+ rackup (2.3.1)
394
+ rails (8.1.3)
395
+ rails-dom-testing (2.3.0)
396
+ rails-html-sanitizer (1.7.0)
397
+ railties (8.1.3)
398
+ rainbow (3.1.1)
399
+ rake (13.4.2)
400
+ rbs (4.0.3)
401
+ rdoc (8.0.0)
402
+ regexp_parser (2.12.0)
403
+ reissue (0.4.22)
404
+ reline (0.6.3)
405
+ rspec (3.13.2)
406
+ rspec-core (3.13.6)
407
+ rspec-expectations (3.13.5)
408
+ rspec-mocks (3.13.8)
409
+ rspec-rails (7.1.1)
410
+ rspec-support (3.13.7)
411
+ rubocop (1.87.0)
412
+ rubocop-ast (1.50.0)
413
+ rubocop-performance (1.26.1)
414
+ ruby-progressbar (1.13.0)
415
+ securerandom (0.4.1)
416
+ simplecov (0.22.0)
417
+ simplecov-html (0.13.2)
418
+ simplecov_json_formatter (0.1.4)
419
+ standard (1.55.0)
420
+ standard-custom (1.0.2)
421
+ standard-performance (1.9.0)
422
+ thor (1.5.0)
423
+ timeout (0.6.1)
424
+ tsort (0.2.0)
425
+ tzinfo (2.0.6)
426
+ unicode-display_width (3.2.0)
427
+ unicode-emoji (4.2.0)
428
+ uri (1.1.1)
429
+ useragent (0.16.11)
430
+ websocket-driver (0.8.2)
431
+ websocket-extensions (0.1.5)
432
+ xpath (3.2.0)
433
+ zeitwerk (2.8.2)
434
+
435
+ BUNDLED WITH
436
+ 4.0.10
@@ -1,21 +1,24 @@
1
1
  ===============================================================================
2
2
 
3
- ice_cube_select assets have been installed!
3
+ ice_cube_select assets have been copied into your app for customizing.
4
4
 
5
5
  Files copied:
6
6
  - app/assets/stylesheets/ice_cube_select.css
7
7
  - app/assets/javascripts/ice_cube_select.js
8
8
  - app/assets/javascripts/ice_cube_select_dialog.js
9
9
 
10
- Next steps:
10
+ These copies take precedence over the gem's versions and will NOT update
11
+ when you upgrade the gem. Re-run this generator (or diff against the gem)
12
+ after upgrading if you want the latest changes.
11
13
 
12
- 1. Include the assets in your layout (app/views/layouts/application.html.erb):
14
+ If you only include the layout tags below without copying, the assets are
15
+ served straight from the gem and update automatically:
13
16
 
14
17
  <%= stylesheet_link_tag 'ice_cube_select', 'data-turbo-track': 'reload' %>
15
18
  <%= javascript_include_tag 'ice_cube_select', 'data-turbo-track': 'reload' %>
16
19
  <%= javascript_include_tag 'ice_cube_select_dialog', 'data-turbo-track': 'reload' %>
17
20
 
18
- 2. Use in your forms:
21
+ Use in your forms:
19
22
 
20
23
  <%= f.ice_cube_select :schedule, :start_date => Date.today %>
21
24
 
@@ -5,7 +5,7 @@ module IceCubeSelect
5
5
  class InstallGenerator < Rails::Generators::Base
6
6
  source_root File.expand_path("../../../../app/assets", __FILE__)
7
7
 
8
- desc "Copies ice_cube_select assets to your application for use with Propshaft"
8
+ desc "Copies ice_cube_select assets into your application so you can customize them (optional; the assets are served from the engine by default)"
9
9
 
10
10
  def copy_stylesheets
11
11
  copy_file "stylesheets/ice_cube_select.css",
@@ -0,0 +1,143 @@
1
+ require "ice_cube"
2
+
3
+ module IceCube
4
+ # A monthly recurrence whose occurrences are fixed day-offsets from the
5
+ # Nth <weekday> of each month. Unlike IceCube's ordinal monthly rules,
6
+ # which count each weekday from day 1 of the month, this counts from a
7
+ # single anchor date, so a pattern like "Sun/Tue/Thu and the following
8
+ # Sun, starting after the 1st Saturday" lands on the same relative days
9
+ # every month.
10
+ class AnchoredMonthlyRule < Rule
11
+ attr_reader :anchor_weekday, :anchor_ordinal, :day_offsets
12
+
13
+ def initialize(anchor_weekday:, anchor_ordinal:, day_offsets:, interval: 1)
14
+ @anchor_weekday = anchor_weekday.to_i
15
+ # The occurrence search walks day by day to the anchor weekday, so a
16
+ # weekday outside 0 (Sunday)-6 (Saturday) would never match and loop
17
+ # forever. Reject it up front.
18
+ unless (0..6).cover?(@anchor_weekday)
19
+ raise ArgumentError, "anchor_weekday must be 0 (Sunday) through 6 (Saturday), got #{anchor_weekday.inspect}"
20
+ end
21
+ @anchor_ordinal = anchor_ordinal.to_i
22
+ @day_offsets = Array(day_offsets).map(&:to_i).sort
23
+ @interval = interval.to_i
24
+ @until_time = nil
25
+ end
26
+
27
+ attr_reader :interval, :until_time
28
+
29
+ def until(time)
30
+ @until_time = time
31
+ self
32
+ end
33
+
34
+ # Counts are not supported for this rule (YAGNI); never force a
35
+ # full-history walk.
36
+ def full_required?
37
+ false
38
+ end
39
+
40
+ def occurrence_count
41
+ nil
42
+ end
43
+
44
+ # This rule uses no IceCube validations; return an empty set so callers
45
+ # that introspect validations (e.g. selected_days) stay safe.
46
+ def validations
47
+ {}
48
+ end
49
+
50
+ # IceCube enumeration contract: the earliest occurrence at or after
51
+ # `time` (and at or after the schedule's `start_time`), or nil when the
52
+ # rule has terminated. Computed arithmetically rather than through the
53
+ # monthly interval spinner.
54
+ def next_time(time, start_time, closing_time)
55
+ from = (time < start_time) ? start_time : time
56
+ month = Date.new(from.year, from.month, 1)
57
+ # A match always exists within a couple of months; 24 is a safe bound.
58
+ 24.times do
59
+ occurrence_dates(month).each do |date|
60
+ occ = at_wall_clock(start_time, date)
61
+ next if occ < from || occ < start_time
62
+ return nil if @until_time && occ > @until_time
63
+ return occ
64
+ end
65
+ month = month.next_month
66
+ end
67
+ nil
68
+ end
69
+
70
+ def to_hash
71
+ hash = {
72
+ rule_type: self.class.name,
73
+ interval: @interval,
74
+ anchor_weekday: @anchor_weekday,
75
+ anchor_ordinal: @anchor_ordinal,
76
+ day_offsets: @day_offsets
77
+ }
78
+ hash[:until] = TimeUtil.serialize_time(@until_time) if @until_time
79
+ hash
80
+ end
81
+
82
+ def self.from_hash(original_hash)
83
+ hash = IceCube::FlexibleHash.new(original_hash)
84
+ rule = new(
85
+ anchor_weekday: hash[:anchor_weekday],
86
+ anchor_ordinal: hash[:anchor_ordinal],
87
+ day_offsets: hash[:day_offsets],
88
+ interval: hash[:interval] || 1
89
+ )
90
+ rule.until(TimeUtil.deserialize_time(hash[:until])) if hash[:until]
91
+ rule
92
+ end
93
+
94
+ def to_s
95
+ "Monthly, after the #{ordinalize(@anchor_ordinal)} #{Date::DAYNAMES[@anchor_weekday]}: " \
96
+ "day offsets #{@day_offsets.join(", ")}"
97
+ end
98
+
99
+ private
100
+
101
+ # The occurrence dates for `month` (a Date on the 1st): each day-offset
102
+ # added to the anchor. Empty when the month lacks the requested ordinal
103
+ # weekday (e.g. no 5th Saturday).
104
+ def occurrence_dates(month)
105
+ first = month
106
+ first += 1 until first.wday == @anchor_weekday
107
+ anchor = first + (@anchor_ordinal - 1) * 7
108
+ return [] unless anchor.month == month.month
109
+ @day_offsets.map { |offset| anchor + offset }
110
+ end
111
+
112
+ # The occurrence instant on `date`: the schedule start's wall clock,
113
+ # re-derived in its zone for that date (DST-correct).
114
+ def at_wall_clock(start_time, date)
115
+ start_time.change(year: date.year, month: date.month, day: date.day)
116
+ end
117
+
118
+ def ordinalize(n)
119
+ %w[0th 1st 2nd 3rd 4th 5th][n] || "#{n}th"
120
+ end
121
+ end
122
+ end
123
+
124
+ # ice_cube's Rule.from_hash only understands its seven built-in interval
125
+ # types and rejects any other rule_type. Intercept our rule_type before
126
+ # that check and dispatch to AnchoredMonthlyRule; defer everything else to
127
+ # the original implementation.
128
+ module IceCube
129
+ class Rule
130
+ class << self
131
+ alias_method :from_hash_without_anchored, :from_hash
132
+
133
+ def from_hash(original_hash)
134
+ hash = IceCube::FlexibleHash.new(original_hash)
135
+ if hash[:rule_type].to_s == "IceCube::AnchoredMonthlyRule"
136
+ IceCube::AnchoredMonthlyRule.from_hash(original_hash)
137
+ else
138
+ from_hash_without_anchored(original_hash)
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -1,3 +1,3 @@
1
1
  module IceCubeSelect
2
- VERSION = "4.0.3"
2
+ VERSION = "4.0.4"
3
3
  end
@@ -1,5 +1,6 @@
1
1
  require "ice_cube_select/engine"
2
2
  require "ice_cube"
3
+ require "ice_cube/anchored_monthly_rule"
3
4
 
4
5
  module IceCubeSelect
5
6
  def self.dirty_hash_to_rule(params)
@@ -35,6 +36,9 @@ module IceCubeSelect
35
36
 
36
37
  params[:interval] = params[:interval].to_i if params[:interval]
37
38
  params[:week_start] = params[:week_start].to_i if params[:week_start]
39
+ params[:anchor_weekday] = params[:anchor_weekday].to_i if params[:anchor_weekday]
40
+ params[:anchor_ordinal] = params[:anchor_ordinal].to_i if params[:anchor_ordinal]
41
+ params[:day_offsets] = to_int_array(params[:day_offsets]) if params[:day_offsets]
38
42
 
39
43
  params[:validations] ||= {}
40
44
  params[:validations] = deep_symbolize_keys(params[:validations])
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ice_cube-select
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.3
4
+ version: 4.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jim Gay
@@ -59,9 +59,11 @@ files:
59
59
  - config/locales/fr.yml
60
60
  - config/locales/nl.yml
61
61
  - gemfiles/Gemfile.rails-8.0
62
+ - gemfiles/Gemfile.rails-8.0.lock
62
63
  - lib/generators/ice_cube_select/INSTALL
63
64
  - lib/generators/ice_cube_select/install_generator.rb
64
65
  - lib/helpers/ice_cube_select_helper.rb
66
+ - lib/ice_cube/anchored_monthly_rule.rb
65
67
  - lib/ice_cube/select.rb
66
68
  - lib/ice_cube_select.rb
67
69
  - lib/ice_cube_select/engine.rb
@@ -74,8 +76,15 @@ metadata: {}
74
76
  post_install_message: |
75
77
  ice_cube_select installed successfully!
76
78
 
77
- To copy assets to your app (required for Rails 8+ Propshaft):
78
- rails generate ice_cube_select:install
79
+ The assets are served automatically from the engine (Propshaft and
80
+ Sprockets both pick them up). Just reference them in your layout:
81
+
82
+ <%= stylesheet_link_tag "ice_cube_select" %>
83
+ <%= javascript_include_tag "ice_cube_select" %>
84
+ <%= javascript_include_tag "ice_cube_select_dialog" %>
85
+
86
+ Only run `rails generate ice_cube_select:install` if you want to copy the
87
+ assets into your app to customize them.
79
88
 
80
89
  For more info: https://github.com/saturnflyer/ice_cube_select
81
90
  rdoc_options: []