recourse 4.1.2 → 4.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: af93482d287e4e17a767eda579b65e6e1be6a1b137ba0b60f4bcb81d079afbc7
4
- data.tar.gz: e345935ababe4e104054f2072cdb2a0f270cd0fa651946a40b96178fef318a38
3
+ metadata.gz: 00e1253a6c0a7e462efb88f9c46765d51cbc90b20073cf72fefc38ac468d31b2
4
+ data.tar.gz: 51271fdc26d6a06cf53089e747f70a842b51d71ffa1fe163e4dc08927e1ba005
5
5
  SHA512:
6
- metadata.gz: e9270aa3d092c1c760ca0a9b2a7fcdb754afb8fcbdeb48def3a8afb6ad7218ec96d48e10ed20bd13c1893c49e025bacc0016c5fe895fe5cabd56cb3f8cd7cbef
7
- data.tar.gz: 9218b731e6d782dc16b1fa26480367dbe5867cc8945fd7102d3d03b5dd64d399c3496e0edb4b24e13a67f6176650c11b005c633cbe7511405f1ca49454b13f73
6
+ metadata.gz: 3526cb175143ce2fae2919654b9f7afb7c1e44e6188462084e56c784f81aaf624cc128b760499991d79bf809fab97b14a50156c15f1e4969fff710309bfee4e2
7
+ data.tar.gz: a4aac6c9576b269c9df0f66fa4f0e60ca4e8da4461500be1a534caa2f25f33ff6c08d88e4e7ec431edc15d3ad4cb6c9af80725245f45d82557586640c79cab46
data/CHANGELOG.md CHANGED
@@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file.
5
5
  For more information about changelogs, check [Keep a Changelog](http://keepachangelog.com) and
6
6
  [Vandamme](http://tech-angels.github.io/vandamme).
7
7
 
8
+ ## 4.3.0 - 2026-09-08
9
+
10
+ * [Feature] On a phone a stacked cell reads as one line, `Estimate hi: 600`
11
+ * [Feature] On a phone the sidebar's icons carry their words: `Dark mode`, `Light mode`, `Exit`
12
+ * [Fix] A page never scrolls sideways on a phone, and Safari no longer zooms out of it
13
+
14
+ Safari laid the stacked table's hidden header row out at its full width; the page
15
+ now clips what would run past the window.
16
+
17
+ * [Fix] Safari no longer zooms in on the search box or a filter when it is focused
18
+
19
+ The small controls were 14px, and Safari zooms on any field under 16; on a phone
20
+ they take the body's size.
21
+
22
+ * [Fix] The footer's sentence stands above the pages on a phone rather than beside them
23
+ * [Fix] A filter's menu drops over the first rows of the table rather than under them
24
+ * [Feature] A date or a time is headed `Created` and `Updated`, not `Created at` and `Updated at`
25
+ * [Fix] A touch screen gets no hover tint on a table's rows
26
+
27
+ ## 4.2.0 - 2026-09-08
28
+
29
+ * [Feature] The log-out button sits beside the light/dark toggle
30
+
31
+ The two share the sidebar's foot, each centered in its own half, and a hover tints
32
+ either under a rounded corner.
33
+
34
+ * [Fix] A bookmark click no longer marks the row green
35
+ * [Fix] The log-out button is submitted by the browser, not by Turbo
36
+
37
+ A log out ends with a redirect to wherever the host signs people in, which is
38
+ another origin a fetch cannot follow, so the click left the page where it was.
39
+
40
+ The kept tint taking or leaving is the whole report.
41
+
8
42
  ## 4.1.2 - 2026-09-08
9
43
 
10
44
  * [Fix] The engine requires turbo-rails after Rails, not before
@@ -1,6 +1,5 @@
1
1
  import { Controller } from '/recourse/stimulus.js'
2
2
  import { flash } from '/recourse/flash.js'
3
- import { marked } from '/recourse/written.js'
4
3
 
5
4
  // The square that keeps a row, answered before the server does. The icon flips under
6
5
  // the cursor and the request goes in the background, so the table is never redrawn and
@@ -78,13 +77,11 @@ export default class extends Controller {
78
77
  })
79
78
  if (!response.ok) return this.revert(kept)
80
79
 
81
- // Two halves of one report, and the reason there is no toast. The tint lasts and
82
- // says which rows are kept; the mark passes and says this one was written just
83
- // now the same outline a create or an update leaves while its message stands.
84
- // The icon flipped on the click and would have flipped under a request that
85
- // never landed, so both are the half only the server can give.
80
+ // The report, and the reason there is no toast: the row takes or loses its tint,
81
+ // which says which rows are kept. The icon flipped on the click and would have
82
+ // flipped under a request that never landed, so this is the half only the server
83
+ // can give.
86
84
  this.row?.classList.toggle('recourse-kept', kept)
87
- marked(this.row)
88
85
  } catch {
89
86
  this.revert(kept)
90
87
  }
@@ -1,7 +1,5 @@
1
- // The mark a write leaves on the row it landed on, and how long it stands for. Two
2
- // things write rows here and both say so this way: a create or an update, which has a
3
- // toast to keep time with, and the square that keeps a row, which has none — so the
4
- // marking is here and the clock belongs to whoever calls it.
1
+ // The mark a create or an update leaves on the row it landed on, and how long the toast
2
+ // saying so stands the one clock, which the toast controller reads too.
5
3
  export const DELAY = 2000
6
4
 
7
5
  // Marked, and handed back the way to let it go. Letting go swaps one class for the
@@ -20,10 +18,3 @@ export function mark(row) {
20
18
  row.classList.add('recourse-written-out')
21
19
  }
22
20
  }
23
-
24
- // And the same, on a clock of its own, for a write with no message to keep time with.
25
- export function marked(row) {
26
- const fade = mark(row)
27
-
28
- setTimeout(fade, DELAY)
29
- }
@@ -101,6 +101,25 @@
101
101
  --bs-breadcrumb-link-hover-color: var(--bs-breadcrumb-link-active-color);
102
102
  }
103
103
 
104
+ /* Nothing here is meant to be wider than the window: the one thing that is, a
105
+ table, scrolls inside its own wrapper. Safari lays a stacked table's hidden
106
+ header row out at its full width all the same, and a page that can be
107
+ scrolled to it is a page Safari zooms out of and draws the navbar short on. */
108
+ html, .recourse-shell { overflow-x: hidden; }
109
+
110
+ /* A finger does not hover. The tint a pointer leaves on the row under it would
111
+ stay on whatever row was last touched, so a touch screen gets none. */
112
+ @media (hover: none) {
113
+ .table-hover > tbody > tr:hover > * {
114
+ --bs-table-color-state: initial;
115
+ --bs-table-bg-state: initial;
116
+ }
117
+ }
118
+
119
+ /* The navbar and the sidebar stack over the main column: a menu opened from a
120
+ filter or a link's tooltip drops over the first rows rather than under them. */
121
+ .recourse-shell > nav, .recourse-sidebar { position: relative; z-index: 2; }
122
+
104
123
  /* An action column is as wide as a row is tall — an icon in a square — and a
105
124
  counter column starts at the same square, widening only when a figure like
106
125
  38,405 needs the room: a table laying out to 100% reads the width as a
@@ -116,6 +135,23 @@
116
135
  }
117
136
  }
118
137
 
138
+ /* Stacked, a cell reads as one line — `Estimate hi: 600`, the heading semibold
139
+ and the value after it — where Bootstrap puts the heading on a line of its own
140
+ and leaves the row's first cell with no heading at all, bold in its place: a
141
+ status opening a row would be a bare badge. Every cell is headed here. And a
142
+ value with no space in it — an address, a token — breaks rather than widening
143
+ the row past the phone. */
144
+ @container (width < 576px) {
145
+ .sm\:table-stacked > tbody > tr > td[data-cell]::before {
146
+ content: attr(data-cell) ': ';
147
+ display: inline;
148
+ font-weight: var(--bs-font-weight-semibold);
149
+ }
150
+ .sm\:table-stacked > tbody > tr > td:first-child { font-weight: inherit; }
151
+ .sm\:table-stacked > tbody > tr > td:not(:first-child) + td::before { margin-block-start: 0; }
152
+ .sm\:table-stacked > tbody > tr > td { overflow-wrap: anywhere; }
153
+ }
154
+
119
155
  /* A counter says what it counts in one of two ways, and the table's own width is
120
156
  what picks: the icon and a bare figure where the column is a square, the
121
157
  counted model's words where there is room to read them. Bootstrap's `xl`, so
@@ -224,6 +260,9 @@
224
260
  .recourse-search { max-width: none; }
225
261
  .recourse-search-box,
226
262
  .recourse-search .combobox-toggle { flex: 1 1 100%; }
263
+ /* Safari zooms in on a field whose text is under 16px, and the small controls
264
+ are 14px: on a phone they take the body's size, and focusing one moves nothing. */
265
+ .recourse-search { --bs-btn-input-sm-font-size: var(--bs-body-font-size); }
227
266
  }
228
267
 
229
268
  /* Held Option marks the letter each sidebar link answers to. Marked rather than
@@ -335,12 +374,23 @@
335
374
  .table > tbody > .recourse-written-out > * { animation: none; }
336
375
  }
337
376
 
338
- /* The scheme toggle sits at the foot of the sidebar and centered across it while
339
- the sidebar is a column, and is simply the last item of the row while it is not
377
+ /* The foot of the sidebar holds the scheme toggle and the way out side by side,
378
+ each centered in its own half of the row: the toggle alone is in the middle, and
379
+ the two together are at a quarter and at three quarters. Each control sits in
380
+ a wrapper — the toggle's own, the form `button_to` draws — and the wrapper is
381
+ the half, so a button is only as wide as its icon and so is its hover. */
382
+ .recourse-foot { display: flex; }
383
+ .recourse-foot > * { flex: 1 1 0; display: flex; justify-content: center; }
384
+ .recourse-foot .nav-link { border-radius: var(--bs-radius-5); }
385
+ /* A hand cursor alone says little on an icon: a tint under it says it is a control. */
386
+ .recourse-foot .nav-link:hover { background-color: var(--bs-bg-1); color: var(--bs-fg-body); }
387
+
388
+ /* The foot sits at the bottom of the sidebar and stretches across it while the
389
+ sidebar is a column, and is simply the last item of the row while it is not
340
390
  — the query matches the `md:` the sidebar's own classes use. Both lines are
341
391
  needed: `.nav-item` ships `flex: auto`, so every item would take an equal share
342
392
  of a full-height nav and the auto margin would never have anything left to
343
- push against. Pin the items to their own height and the toggle takes the rest. */
393
+ push against. Pin the items to their own height and the foot takes the rest. */
344
394
  @media (width >= 768px) {
345
395
  /* A floor rather than a height, and never wrapped. `.nav` ships `flex-wrap:
346
396
  wrap`, and a column that may wrap, whose items are pinned to their own height
@@ -351,17 +401,28 @@
351
401
  .recourse-sidebar .nav-item { flex: 0 0 auto; }
352
402
  /* And it rides at the foot of the screen, which the auto margin alone is
353
403
  enough to do: the nav's floor is the sidebar's full height, so while the
354
- links leave room the margin takes all of it and the toggle sits on the fold,
404
+ links leave room the margin takes all of it and the foot sits on the fold,
355
405
  rising with it as the window is shortened. Once the links fill the nav there
356
- is no room left to take, and the toggle comes to rest immediately below the
406
+ is no room left to take, and the foot comes to rest immediately below the
357
407
  last of them and goes under the fold with it, to be scrolled to.
358
408
  Deliberately not sticky. Sticking it to the bottom of the viewport would lift
359
409
  it off that resting place and hold it over the links it had passed — which is
360
410
  what the background color was for, masking whichever one it covered. Both go
361
411
  together: nothing is painted over, so nothing needs painting out. */
362
- .recourse-sidebar .recourse-scheme {
412
+ .recourse-sidebar .recourse-foot {
363
413
  margin-block-start: auto;
364
- align-self: center;
414
+ align-self: stretch;
415
+ }
416
+ }
417
+
418
+ /* While the sidebar is a row its foot has room for words, so each icon is followed
419
+ by its own; as a column the words are read but not seen, the way Bootstrap's
420
+ `.visually-hidden` does it. */
421
+ .recourse-foot-word { margin-inline-start: .4em; }
422
+ @media (width >= 768px) {
423
+ .recourse-foot-word {
424
+ position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
425
+ overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
365
426
  }
366
427
  }
367
428
 
@@ -1,6 +1,7 @@
1
1
  <%# locals: (pagy:) %>
2
2
  <% if pagy %>
3
- <div class='d-flex align-items-center' <%= tag.attributes data: limit_data(pagy) %>>
3
+ <%# A column on a phone, the sentence above the pages, and one row from `sm` up. -%>
4
+ <div class='d-flex flex-column sm:flex-row sm:align-items-center' <%= tag.attributes data: limit_data(pagy) %>>
4
5
  <%# The gem's own sentence from pagy's public readers, every figure delimited —
5
6
  rewriting pagy's English with a regex delimited one figure, only in English.
6
7
  The switch follows it after a dot: how much of the table is showing, then the
@@ -17,6 +18,6 @@
17
18
  <%= t 'recourse.displaying', count: pagy.count %>
18
19
  <% end %>
19
20
  </div>
20
- <div class='pe-2'><%== pagy.series_nav(:bootstrap, classes: 'pagination mb-0') if pagy.last > 1 %></div>
21
+ <div class='px-2 mt-2 sm:mt-0'><%== pagy.series_nav(:bootstrap, classes: 'pagination mb-0') if pagy.last > 1 %></div>
21
22
  </div>
22
23
  <% end %>
@@ -9,32 +9,37 @@
9
9
  </li>
10
10
  <% end %>
11
11
 
12
- <%# A reader's own say in how the page looks: the last item of the sidebar, and at the
13
- foot of it wherever it is a column. Which side of the fold that lands on is the
14
- layout's business rather than this file's, and so is which of the two icons shows
15
- the mode may be the system's, which only CSS knows, so both are drawn. -%>
16
- <li class='nav-item recourse-scheme'>
17
- <%= tag.button type: 'button', class: 'nav-link border-0 bg-transparent',
18
- data: scheme_data do %>
19
- <span class='recourse-scheme-light'>
20
- <i class='bi bi-moon-fill'></i>
21
- <span class='visually-hidden'><%= t 'recourse.darken' %></span>
22
- </span>
23
- <span class='recourse-scheme-dark'>
24
- <i class='bi bi-sun-fill'></i>
25
- <span class='visually-hidden'><%= t 'recourse.lighten' %></span>
26
- </span>
27
- <% end %>
28
- </li>
29
-
30
- <%# And the way out, where the host drew a route named `exit`: a button, since ending
31
- a session is never a GET, and an icon like the toggle beside it. -%>
32
- <% if exit? %>
33
- <li class='nav-item recourse-exit'>
34
- <%= button_to exit_path, method: :delete, class: 'nav-link border-0 bg-transparent' do %>
12
+ <%# The foot of the sidebar: a reader's own say in how the page looks and, where the
13
+ host drew a route named `exit`, the way out beside it a button, since ending a
14
+ session is never a GET. Each takes half the row and centers its icon in it, so the
15
+ toggle alone sits in the middle and the two together sit at a quarter and at three
16
+ quarters. Which side of the fold the foot lands on is the layout's business, and so
17
+ is which of the two mode icons shows — the mode may be the system's, which only CSS
18
+ knows, so both are drawn. -%>
19
+ <li class='nav-item recourse-foot'>
20
+ <%# In a wrapper of its own, as the way out is in its form: the wrapper is the half
21
+ of the row, and the button is only as wide as its icon. -%>
22
+ <div>
23
+ <%= tag.button type: 'button', class: 'nav-link border-0 bg-transparent',
24
+ data: scheme_data do %>
25
+ <span class='recourse-scheme-light'>
26
+ <i class='bi bi-moon-fill'></i>
27
+ <span class='recourse-foot-word'><%= t 'recourse.darken' %></span>
28
+ </span>
29
+ <span class='recourse-scheme-dark'>
30
+ <i class='bi bi-sun-fill'></i>
31
+ <span class='recourse-foot-word'><%= t 'recourse.lighten' %></span>
32
+ </span>
33
+ <% end %>
34
+ </div>
35
+ <%# Submitted by the browser rather than by Turbo: a log out ends with a redirect to
36
+ wherever the host signs people in, which is another origin a fetch cannot follow. -%>
37
+ <% if exit? %>
38
+ <%= button_to exit_path, method: :delete, class: 'nav-link border-0 bg-transparent',
39
+ form: { data: { turbo: false } } do %>
35
40
  <i class='bi bi-box-arrow-right'></i>
36
- <span class='visually-hidden'><%= t 'recourse.exit' %></span>
41
+ <span class='recourse-foot-word'><%= t 'recourse.exit' %></span>
37
42
  <% end %>
38
- </li>
39
- <% end %>
43
+ <% end %>
44
+ </li>
40
45
  </ul>
@@ -21,10 +21,10 @@ en:
21
21
  create: Create
22
22
  created: "%{model} was created."
23
23
  created_error: "%{model} could not be created."
24
- darken: Darken the page
24
+ darken: Dark mode
25
25
  delete: Delete %{model}
26
26
  deleted: "%{model} was deleted."
27
- exit: Log out
27
+ exit: Exit
28
28
  displaying:
29
29
  one: Displaying 1 item
30
30
  other: Displaying %{count} items
@@ -37,7 +37,7 @@ en:
37
37
  items:
38
38
  one: 1 item
39
39
  other: "%{count} items"
40
- lighten: Lighten the page
40
+ lighten: Light mode
41
41
  missing_model: You declared `recourses :%{name}` in your routes file, but this app
42
42
  has no %{model} model.
43
43
  nested: You nested `recourses %{names}` inside `resources :%{parent}`, which does
@@ -59,11 +59,20 @@ module Recourse
59
59
  # column holds a number and the heading says what the number is of.
60
60
  def resource_column_title(column)
61
61
  counted = resource_model.recourse_counters[column]
62
- return resource_model.human_attribute_name column unless counted
62
+ return dated_title column unless counted
63
63
 
64
64
  Recourse.model_title counted.klass
65
65
  end
66
66
 
67
+ # The model's own name for a column, less the `at` or `on` a date's name ends in:
68
+ # `Created`, not `Created at`, since when it happened is what the column holds.
69
+ def dated_title(column)
70
+ title = resource_model.human_attribute_name column
71
+ dated = column.end_with?('_at', '_on') && Columns::DATE_KINDS.include?(attribute_type(column))
72
+
73
+ dated ? title.sub(/ (at|on)\z/, '') : title
74
+ end
75
+
67
76
  # Value for one cell, formatted according to what the column holds.
68
77
  def resource_cell(resource, column)
69
78
  association = belongs_to_association column
@@ -1,4 +1,4 @@
1
1
  module Recourse
2
2
  # Version of the gem, read by the gemspec and by hosts checking compatibility.
3
- VERSION = '4.1.2'
3
+ VERSION = '4.3.0'
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: recourse
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.1.2
4
+ version: 4.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - claudiob