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 +4 -4
- data/CHANGELOG.md +34 -0
- data/app/javascript/recourse/bookmark_controller.js +4 -7
- data/app/javascript/recourse/written.js +2 -11
- data/app/views/layouts/recourses.html.erb +68 -7
- data/app/views/recourses/_footer.html.erb +3 -2
- data/app/views/recourses/_sidebar.html.erb +31 -26
- data/config/locales/recourse.en.yml +3 -3
- data/lib/recourse/helpers/cells.rb +10 -1
- data/lib/recourse/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 00e1253a6c0a7e462efb88f9c46765d51cbc90b20073cf72fefc38ac468d31b2
|
|
4
|
+
data.tar.gz: 51271fdc26d6a06cf53089e747f70a842b51d71ffa1fe163e4dc08927e1ba005
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
-
//
|
|
82
|
-
// says which rows are kept
|
|
83
|
-
//
|
|
84
|
-
//
|
|
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
|
|
2
|
-
//
|
|
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
|
|
339
|
-
|
|
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
|
|
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
|
|
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
|
|
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-
|
|
412
|
+
.recourse-sidebar .recourse-foot {
|
|
363
413
|
margin-block-start: auto;
|
|
364
|
-
align-self:
|
|
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
|
-
|
|
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='
|
|
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
|
-
<%#
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
<span class='
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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='
|
|
41
|
+
<span class='recourse-foot-word'><%= t 'recourse.exit' %></span>
|
|
37
42
|
<% end %>
|
|
38
|
-
|
|
39
|
-
|
|
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:
|
|
24
|
+
darken: Dark mode
|
|
25
25
|
delete: Delete %{model}
|
|
26
26
|
deleted: "%{model} was deleted."
|
|
27
|
-
exit:
|
|
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:
|
|
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
|
|
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
|
data/lib/recourse/version.rb
CHANGED