recourse 5.12.2 → 7.0.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 +59 -0
- data/README.md +29 -15
- data/app/views/layouts/recourses.html.erb +28 -20
- data/app/views/recourses/_combobox.html.erb +1 -1
- data/app/views/recourses/edit.html.erb +1 -4
- data/app/views/recourses/index.html.erb +1 -9
- data/app/views/recourses/show.html.erb +9 -14
- data/lib/recourse/assets.rb +9 -18
- data/lib/recourse/columns.rb +1 -1
- data/lib/recourse/counters.rb +40 -0
- data/lib/recourse/engine.rb +4 -0
- data/lib/recourse/helpers/actions.rb +5 -5
- data/lib/recourse/helpers/cards.rb +10 -0
- data/lib/recourse/helpers/cells.rb +2 -2
- data/lib/recourse/helpers/counters.rb +3 -2
- data/lib/recourse/helpers/deletions.rb +4 -6
- data/lib/recourse/helpers/filters.rb +1 -1
- data/lib/recourse/helpers/flashes.rb +3 -11
- data/lib/recourse/helpers/kinds.rb +1 -1
- data/lib/recourse/helpers/pictures.rb +1 -7
- data/lib/recourse/helpers/rows.rb +1 -1
- data/lib/recourse/helpers/searches.rb +13 -3
- data/lib/recourse/helpers/sorts.rb +1 -1
- data/lib/recourse/helpers/tabs.rb +12 -8
- data/lib/recourse/recoursive.rb +0 -12
- data/lib/recourse/searchable/terms.rb +9 -21
- data/lib/recourse/searchable.rb +1 -1
- data/lib/recourse/searches.rb +31 -0
- data/lib/recourse/version.rb +1 -1
- data/lib/recourse.rb +4 -1
- metadata +17 -2
- data/app/views/recourses/_flash.html.erb +0 -27
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d4c5d237ba9ace6d0bde291127548ea5202ea729de90541d11240a5d2db0dae3
|
|
4
|
+
data.tar.gz: 7f266eaa7ba56f045c692702fa94c4163d09621bedfd3eb963ab025af5efba39
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c03bfff3a9f95fa135fd7aaf1b5b16e8c905dab9431d13bdc139aaf58cf5188d36164e1921260b6ba21fe3829bdea087bcefbf9f2082ddc73e06d6201ccee885
|
|
7
|
+
data.tar.gz: c79417ff860145a2da9a5e987d842f9d7351e1a92c91916a65714ae83b68786f4f6c557d6e60cdd35dbed670d24974831490235a5127cb4f8437962576e7dd7d
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,65 @@ For more information about changelogs, check [Keep a Changelog](http://keepachan
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## 7.0.0 - 2026-09-19
|
|
11
|
+
|
|
12
|
+
* [BREAKING] What a page counts is the gem's to work out
|
|
13
|
+
|
|
14
|
+
`recourse_counters` is gone from every model. A column is counted where it holds a
|
|
15
|
+
counter cache, as before, and now also where it is named `<association>_count` for an
|
|
16
|
+
association the model has -- so a figure the app keeps itself, a `has_many through`
|
|
17
|
+
Rails will not cache among them, is drawn as a count without being declared.
|
|
18
|
+
|
|
19
|
+
The rule a host signs up for: a `*_count` column whose prefix names an association must
|
|
20
|
+
hold the count of that association. One naming nothing is an ordinary number, and one
|
|
21
|
+
naming an association it does not count wants another name -- the heading and the link
|
|
22
|
+
a counter draws would both say the wrong thing. `recourse_hidden` takes such a column
|
|
23
|
+
off the screens.
|
|
24
|
+
|
|
25
|
+
* [BREAKING] A record's card is the layout's, not the template's
|
|
26
|
+
|
|
27
|
+
A host writing its own `show`, `edit` or nested `index` for a recoursed model used to
|
|
28
|
+
replace the whole page, card and all, and wrote `render layout: 'recourses/card'` back by
|
|
29
|
+
hand to get the tabs and the buttons beside the trail. The card is drawn around whatever
|
|
30
|
+
template answers now, so a host's page is its body and nothing else.
|
|
31
|
+
|
|
32
|
+
Every host view still wrapping itself draws a card inside a card: drop the wrapper. A page
|
|
33
|
+
that wants the width to itself assigns `@recourse_card = false`.
|
|
34
|
+
|
|
35
|
+
`card_record` is what the card is about -- the parent on a page nested under one, the
|
|
36
|
+
record itself on its look and its change -- which is the rule every hand-written wrapper
|
|
37
|
+
was already passing.
|
|
38
|
+
|
|
39
|
+
## 6.0.0 - 2026-09-19
|
|
40
|
+
|
|
41
|
+
* [Change] These pages are drawn from bh
|
|
42
|
+
|
|
43
|
+
They were drawn from a package named after one company's design system, pinned by hand in
|
|
44
|
+
`Recourse::BUNDLE` and fetched from a CDN. They are drawn from `bh` now, which is the
|
|
45
|
+
Bootstrap layer under that package and is a dependency of this gem, so the version in the
|
|
46
|
+
lock file is the version on the page and nothing is fetched across a network.
|
|
47
|
+
|
|
48
|
+
`Recourse::BUNDLE` is gone. `Recourse.assets` stays, defaulting to bh's own engine, for a
|
|
49
|
+
host serving those three folders from somewhere else.
|
|
50
|
+
|
|
51
|
+
The host's `recourses/head` partial is drawn last in the head rather than before the
|
|
52
|
+
script, so a stylesheet of the host's outranks the rules above it and a module of its own
|
|
53
|
+
runs after the application this one starts.
|
|
54
|
+
|
|
55
|
+
* [Change] The flash is bh's toasts
|
|
56
|
+
|
|
57
|
+
`recourses/_flash` and `FLASH_THEMES` are gone: the markup was bh's, written twice. What
|
|
58
|
+
is this gem's own stays — the row a write landed on, and the link spliced into the words
|
|
59
|
+
a message says — and rides on `toasts` through its `data:` and its block.
|
|
60
|
+
|
|
61
|
+
* [Feature] Every tab wears its model's icon
|
|
62
|
+
|
|
63
|
+
A tab earned one only where the parent kept a `has_many` of that name, so a nested index
|
|
64
|
+
reached through a join, and every singular resource, read as words alone beside tabs that
|
|
65
|
+
had pictures. The icon is now looked up from the route, which is the same question the
|
|
66
|
+
sidebar and the crumbs already ask.
|
|
67
|
+
|
|
68
|
+
|
|
10
69
|
## 5.6.2 - 2026-09-18
|
|
11
70
|
|
|
12
71
|
* [Fix] A kept table notices a count that changed
|
data/README.md
CHANGED
|
@@ -10,16 +10,16 @@ gem install recourse
|
|
|
10
10
|
|
|
11
11
|
```ruby
|
|
12
12
|
# Gemfile
|
|
13
|
-
gem 'recourse', '~>
|
|
13
|
+
gem 'recourse', '~> 6.0'
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
-
`~>
|
|
16
|
+
`~> 6.0` follows Semantic Versioning: `bundle update` takes every 6.x and never a
|
|
17
17
|
breaking change. Rails 8.1 and Ruby 3.2 are the minimum; the pages need Turbo, which
|
|
18
18
|
`turbo-rails` brings, and nothing else in the host. Everything a page is styled and
|
|
19
19
|
scripted by — Bootstrap 6, its icons, Turbo, Stimulus and the controllers behind these
|
|
20
|
-
screens — is
|
|
21
|
-
pipeline gets the same screens
|
|
22
|
-
|
|
20
|
+
screens — is `bh`, which this gem depends on and whose engine serves them at `/bh/`, so a
|
|
21
|
+
host without an asset pipeline gets the same screens and nothing is fetched across a
|
|
22
|
+
network.
|
|
23
23
|
|
|
24
24
|
## Step 1. Edit your routes
|
|
25
25
|
|
|
@@ -99,6 +99,22 @@ To change the content displayed in the `index` table of a model, override any of
|
|
|
99
99
|
| [`recourse_position`](https://rubydoc.info/gems/recourse/Recourse/Recoursive#recourse_position-instance_method) | `'position'` where the model keeps an integer one | the column a reader drags the rows into order by, or `nil` for a table nobody positions |
|
|
100
100
|
| [`recourse_icon`](https://rubydoc.info/gems/recourse/Recourse/Recoursive#recourse_icon-instance_method) | the model's name | the icon on the sidebar, the crumbs and the tabs |
|
|
101
101
|
|
|
102
|
+
A number is drawn as a count, headed with what it counts and linking to the rows behind
|
|
103
|
+
it, when its column holds a counter cache or is named `<association>_count` for an
|
|
104
|
+
association the model has. So a count the app keeps itself — a `has_many through`, which
|
|
105
|
+
Rails will not cache — needs nothing declared:
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
class Technician < ActiveRecord::Base
|
|
109
|
+
has_many :visits, through: :dispatches # and a visits_count column beside it
|
|
110
|
+
end
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
Which means a `*_count` column whose prefix names an association must hold the count of
|
|
114
|
+
that association. One naming nothing — `word_count`, `retry_count` — is an ordinary
|
|
115
|
+
number, and one naming an association it does not count wants another name or a place in
|
|
116
|
+
`recourse_hidden`.
|
|
117
|
+
|
|
102
118
|
For instance, this would yield a more compact `index` than the default configuration:
|
|
103
119
|
|
|
104
120
|
```ruby
|
|
@@ -344,20 +360,18 @@ The dummy app under `test/dummy` runs on SQLite; the gem names no adapter.
|
|
|
344
360
|
|
|
345
361
|
### Reading an unpublished bundle
|
|
346
362
|
|
|
347
|
-
The pages link
|
|
348
|
-
|
|
349
|
-
that — one in this Gemfile, kept out of it because CI checks out this repo alone:
|
|
363
|
+
The pages link bh's own files, so a stylesheet or a Stimulus controller that is not
|
|
364
|
+
published yet is read by pointing the Gemfile at a checkout of it beside this one:
|
|
350
365
|
|
|
351
366
|
```bash
|
|
352
|
-
echo "gem '
|
|
353
|
-
cd test/dummy && bin/rails db:migrate &&
|
|
367
|
+
echo "gem 'bh', path: '../bh'" >> Gemfile && bundle install
|
|
368
|
+
cd test/dummy && bin/rails db:migrate && bin/rails server
|
|
354
369
|
```
|
|
355
370
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
development database is migrated on its own, which the line above is why.
|
|
371
|
+
A host serving those three folders from somewhere else — a CDN, a bundle of its own
|
|
372
|
+
carrying bh's layer — says so in `Recourse.assets`, and that is the whole of what it
|
|
373
|
+
writes. The dummy's development database is migrated on its own, which the line above is
|
|
374
|
+
why.
|
|
361
375
|
|
|
362
376
|
## License
|
|
363
377
|
|
|
@@ -16,18 +16,10 @@
|
|
|
16
16
|
|
|
17
17
|
<%= yield :head %>
|
|
18
18
|
|
|
19
|
-
<%# Everything the page is styled by
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
when somebody moves that line and not before. Two connections opened before the
|
|
24
|
-
parser finds either tag: the stylesheet's plain one and the CORS one a module
|
|
25
|
-
script and a font are fetched over. They name the CDN outright, being a hint
|
|
26
|
-
about it and nothing more, so a host serving the bundle itself simply never uses
|
|
27
|
-
them. -%>
|
|
28
|
-
<link rel='preconnect' href='https://cdn.jsdelivr.net'>
|
|
29
|
-
<link rel='preconnect' href='https://cdn.jsdelivr.net' crossorigin>
|
|
30
|
-
<link rel='stylesheet' href='<%= Recourse.asset 'css/houseaccount.css' %>'>
|
|
19
|
+
<%# Everything the page is styled by: Bootstrap 6 and bh's rules for this gem's
|
|
20
|
+
markup, served by bh's own engine. `Recourse.assets` is where, and a host
|
|
21
|
+
serving those files from somewhere else says so there. -%>
|
|
22
|
+
<link rel='stylesheet' href='<%= Recourse.asset 'css/bh.css' %>'>
|
|
31
23
|
<%# The one thing these screens set that the house's stylesheet does not: system faces
|
|
32
24
|
at a compact size, for pages dense with figures. 14px as rem, so a browser's own
|
|
33
25
|
text-size setting still scales it. After the stylesheet, since both are `:root`. -%>
|
|
@@ -43,15 +35,17 @@
|
|
|
43
35
|
<%= render 'recourses/scheme' %>
|
|
44
36
|
<%= primary_color_style %>
|
|
45
37
|
|
|
46
|
-
<%# The rest of the head is the host's: whatever it puts in a `recourses/head` partial
|
|
47
|
-
— the icons its tab wears, say — is drawn here, from the host's own views or from a
|
|
48
|
-
gem it loads, and nothing is guessed at where nobody has written one. -%>
|
|
49
|
-
<%= render 'recourses/head' if lookup_context.exists? 'recourses/head', [], true %>
|
|
50
|
-
|
|
51
38
|
<%# Turbo, Bootstrap and a Stimulus application with every controller this markup
|
|
52
39
|
names, started once under `window.Stimulus`. In the head, so a controller exists
|
|
53
40
|
before the body's first `data-controller`. -%>
|
|
54
|
-
<script type='module' src='<%= Recourse.asset 'js/
|
|
41
|
+
<script type='module' src='<%= Recourse.asset 'js/bh.js' %>'></script>
|
|
42
|
+
|
|
43
|
+
<%# The rest of the head is the host's: whatever it puts in a `recourses/head` partial
|
|
44
|
+
— the icons its tab wears, a layer of its own over this one — is drawn here, from
|
|
45
|
+
the host's own views or from a gem it loads, and nothing is guessed at where nobody
|
|
46
|
+
has written one. Last, so a stylesheet of the host's outranks the rules above it and
|
|
47
|
+
a module of its own runs after the application this one started. -%>
|
|
48
|
+
<%= render 'recourses/head' if lookup_context.exists? 'recourses/head', [], true %>
|
|
55
49
|
</head>
|
|
56
50
|
|
|
57
51
|
<%# The reader's zone is told to the server here rather than from a page of its
|
|
@@ -65,6 +59,20 @@
|
|
|
65
59
|
<%= render 'recourses/sidebar' %>
|
|
66
60
|
</aside>
|
|
67
61
|
|
|
62
|
+
<%# A record's pages are drawn in its card whoever wrote the template, so the tabs and
|
|
63
|
+
the buttons beside the trail stay the gem's and a host's own page is its body. Drawn
|
|
64
|
+
here rather than where it is written, since the card is where the bare actions are
|
|
65
|
+
said and the trail below is where they are read. -%>
|
|
66
|
+
<% page = capture do %>
|
|
67
|
+
<% if (record = card_record) %>
|
|
68
|
+
<%= render layout: 'recourses/card', locals: { record: record } do %>
|
|
69
|
+
<%= yield %>
|
|
70
|
+
<% end %>
|
|
71
|
+
<% else %>
|
|
72
|
+
<%= yield %>
|
|
73
|
+
<% end %>
|
|
74
|
+
<% end %>
|
|
75
|
+
|
|
68
76
|
<nav class='navbar border-bottom'>
|
|
69
77
|
<%# `row-gap` rather than a margin on the search: a margin is there whether the
|
|
70
78
|
form wrapped or not, and on the line it shares with the breadcrumb it drops
|
|
@@ -80,9 +88,9 @@
|
|
|
80
88
|
</nav>
|
|
81
89
|
|
|
82
90
|
<main class='py-4 px-3'>
|
|
83
|
-
<%=
|
|
91
|
+
<%= page %>
|
|
84
92
|
</main>
|
|
85
93
|
|
|
86
|
-
<%=
|
|
94
|
+
<%= toasts(data: written_data) { |key, message| written_message key, message } %>
|
|
87
95
|
</body>
|
|
88
96
|
</html>
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
select is named with `[]`, so the browser submits every pick the way Ransack reads a
|
|
6
6
|
list predicate. -%>
|
|
7
7
|
<%= tag.select name: (multiple ? "#{name}[]" : name), id: id, multiple: multiple, required: required,
|
|
8
|
-
class: class_names('form-
|
|
8
|
+
class: class_names('form-control', 'form-control-sm': small, 'is-invalid': invalid),
|
|
9
9
|
aria: { label: aria_label, describedby: described },
|
|
10
10
|
data: { controller: 'combobox', combobox_placeholder_value: placeholder,
|
|
11
11
|
combobox_more_value: t('recourse.more'), combobox_all_value: all,
|
|
@@ -4,7 +4,4 @@
|
|
|
4
4
|
<%= destroy_resource_button resource_record %>
|
|
5
5
|
<% end %>
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
<%= render layout: 'card', locals: { record: resource_parent || resource_record } do %>
|
|
9
|
-
<%= render 'form', resource_key => resource_record %>
|
|
10
|
-
<% end %>
|
|
7
|
+
<%= render 'form', resource_key => resource_record %>
|
|
@@ -22,12 +22,4 @@
|
|
|
22
22
|
cable connection down and reopens it. -%>
|
|
23
23
|
<%= refresh_subscription %>
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
card, beside the record's other pages — its tab, the count, the current one. -%>
|
|
27
|
-
<% if resource_parent %>
|
|
28
|
-
<%= render layout: 'card', locals: { record: resource_parent } do %>
|
|
29
|
-
<%= render 'results', resources: @resources, pagy: @pagy, week: @recourse_week %>
|
|
30
|
-
<% end %>
|
|
31
|
-
<% else %>
|
|
32
|
-
<%= render 'results', resources: @resources, pagy: @pagy, week: @recourse_week %>
|
|
33
|
-
<% end %>
|
|
25
|
+
<%= render 'results', resources: @resources, pagy: @pagy, week: @recourse_week %>
|
|
@@ -1,17 +1,12 @@
|
|
|
1
1
|
<% content_for :title, resource_record_label %>
|
|
2
2
|
|
|
3
|
-
<%#
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
<%= render 'values', resource_key => resource_record %>
|
|
13
|
-
</div>
|
|
14
|
-
<% else %>
|
|
15
|
-
<%= render 'none', models: resource_name %>
|
|
16
|
-
<% end %>
|
|
3
|
+
<%# Nothing to read out where the host found no record: a singular resource is reached
|
|
4
|
+
with no id, so the one it stands for is one it may not have yet, and the page says so
|
|
5
|
+
rather than reading attributes off nothing. -%>
|
|
6
|
+
<% if resource_record %>
|
|
7
|
+
<div class='row recourse-values'>
|
|
8
|
+
<%= render 'values', resource_key => resource_record %>
|
|
9
|
+
</div>
|
|
10
|
+
<% else %>
|
|
11
|
+
<%= render 'none', models: resource_name %>
|
|
17
12
|
<% end %>
|
data/lib/recourse/assets.rb
CHANGED
|
@@ -1,28 +1,19 @@
|
|
|
1
|
-
# Reopened for where the
|
|
1
|
+
# Reopened for where the design layer these pages are drawn from is served from.
|
|
2
2
|
module Recourse
|
|
3
|
-
# The release of `houseaccount` they are drawn from, named once — the layout links its
|
|
4
|
-
# stylesheet and its script, and the sidebar's toggle swaps a palette out of the same
|
|
5
|
-
# release. A published version is never written again, so the bytes behind these URLs
|
|
6
|
-
# cannot move under us, and a change to the bundle reaches these pages when somebody
|
|
7
|
-
# moves this line and not before.
|
|
8
|
-
BUNDLE = '0.17.0'
|
|
9
|
-
|
|
10
3
|
class << self
|
|
11
|
-
# Where
|
|
12
|
-
#
|
|
13
|
-
#
|
|
4
|
+
# Where bh's built files answer from, with no trailing slash. Its own engine by
|
|
5
|
+
# default, which every app holding the gem already serves, so nothing is fetched
|
|
6
|
+
# across a network and no version is written down twice: the gem in the lock file
|
|
7
|
+
# is the one on the page.
|
|
14
8
|
#
|
|
15
|
-
# A host
|
|
16
|
-
#
|
|
17
|
-
# how the bundle is worked on — a stylesheet or a controller can be tried on a real
|
|
18
|
-
# page before the version carrying it is published — and it is the whole of what
|
|
19
|
-
# such a host writes.
|
|
9
|
+
# A host serving those files from somewhere else — a CDN, a bundle of its own that
|
|
10
|
+
# carries bh's layer — points this at it, and that is the whole of what it writes.
|
|
20
11
|
attr_accessor :assets
|
|
21
12
|
end
|
|
22
13
|
|
|
23
|
-
@assets =
|
|
14
|
+
@assets = Bh::Engine::PREFIX.chomp '/'
|
|
24
15
|
|
|
25
|
-
# One file of the
|
|
16
|
+
# One file of the design layer.
|
|
26
17
|
def self.asset(path) = "#{assets}/#{path}"
|
|
27
18
|
|
|
28
19
|
# And the folder one palette is read from, which three places ask for: the layout
|
data/lib/recourse/columns.rb
CHANGED
|
@@ -55,7 +55,7 @@ module Recourse
|
|
|
55
55
|
# what state it is in; and a key is an integer, so it has to be recognised as a key
|
|
56
56
|
# before its type is asked about at all.
|
|
57
57
|
def band(model, name, keys)
|
|
58
|
-
return :counter if model.
|
|
58
|
+
return :counter if Recourse.counters(model).key? name
|
|
59
59
|
return :state if name == model.inheritance_column || model.defined_enums.key?(name)
|
|
60
60
|
return :timestamp if TIMESTAMPS.include? name
|
|
61
61
|
return :reference if keys.include? name
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Reopened for what a page counts, which a table, a heading and a tab all read here.
|
|
2
|
+
module Recourse
|
|
3
|
+
# What a page counts, worked out once per class: a reload replaces the classes the
|
|
4
|
+
# reflections belong to, so the engine empties this where it does.
|
|
5
|
+
@counters = {}
|
|
6
|
+
|
|
7
|
+
# The counts a model's pages draw, as `{ column => association }`. A column holding a
|
|
8
|
+
# counter cache answers, read from the `belongs_to` where `counter_cache` is declared;
|
|
9
|
+
# so does any `*_count` column named after an association the model has, which is how a
|
|
10
|
+
# figure the app keeps itself -- a `has_many through`, which Rails will not cache -- is
|
|
11
|
+
# counted like any other. A `*_count` column naming no association is a number and
|
|
12
|
+
# nothing more, and one naming an association it does not count belongs under another
|
|
13
|
+
# name: the heading and the link a counter draws would both say the wrong thing.
|
|
14
|
+
# @param model [Class] the model the page is about.
|
|
15
|
+
# @return [Hash{String => ActiveRecord::Reflection::AbstractReflection}] the counts.
|
|
16
|
+
def self.counters(model)
|
|
17
|
+
@counters[model.name] ||= cached_counters(model).merge counted_columns(model)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Empties what was read off the reflections of classes a reload has replaced.
|
|
21
|
+
# @return [Hash] the empty table.
|
|
22
|
+
def self.forget_counters = @counters.clear
|
|
23
|
+
|
|
24
|
+
private_class_method def self.cached_counters(model)
|
|
25
|
+
model.reflect_on_all_associations(:has_many).filter_map do |association|
|
|
26
|
+
column = association.inverse_of&.counter_cache_column
|
|
27
|
+
[column, association] if column
|
|
28
|
+
end.to_h
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private_class_method def self.counted_columns(model)
|
|
32
|
+
model.column_names.filter_map do |column|
|
|
33
|
+
name = column.delete_suffix '_count'
|
|
34
|
+
next if name == column
|
|
35
|
+
|
|
36
|
+
association = model.reflect_on_association name
|
|
37
|
+
[column, association] if association
|
|
38
|
+
end.to_h
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/recourse/engine.rb
CHANGED
|
@@ -15,6 +15,10 @@ module Recourse
|
|
|
15
15
|
ActionDispatch::Routing::Mapper.include Scopes, Routes
|
|
16
16
|
end
|
|
17
17
|
|
|
18
|
+
# Reflections belong to the classes a reload replaces, so what was read off them
|
|
19
|
+
# goes with the reload.
|
|
20
|
+
config.to_prepare { Recourse.forget_counters }
|
|
21
|
+
|
|
18
22
|
# `/counties.map` and `/shifts.cal` are the same page in another shape, so each
|
|
19
23
|
# format is a name for HTML: without one Rails has no type for the extension and
|
|
20
24
|
# answers 406.
|
|
@@ -81,11 +81,11 @@ module Recourse
|
|
|
81
81
|
def destroy_button(record)
|
|
82
82
|
path = url_for controller: "/#{destroy_action_path}", action: :destroy, id: record
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
confirm_button_to icon_tag(ICONS[:destroy], class: 'fg-danger'), path,
|
|
85
|
+
confirm: destroy_warning(record), method: :delete,
|
|
86
|
+
class: 'btn btn-sm btn-link btn-icon p-0',
|
|
87
|
+
aria: { label: t('recourse.delete', model: resource_name) },
|
|
88
|
+
form: { data: { turbo_frame: '_top' } }
|
|
89
89
|
end
|
|
90
90
|
end
|
|
91
91
|
end
|
|
@@ -5,6 +5,16 @@ module Recourse
|
|
|
5
5
|
module Cards
|
|
6
6
|
private
|
|
7
7
|
|
|
8
|
+
# The record whose card this page is drawn in: the parent, on a page nested under
|
|
9
|
+
# one, and the record itself where the page is its look or its change. An index of
|
|
10
|
+
# its own, and a form for a record that is not there yet, stand outside a card. A
|
|
11
|
+
# host drawing a page whole says so by assigning `@recourse_card = false`.
|
|
12
|
+
def card_record
|
|
13
|
+
return if controller_assign('recourse_card') == false
|
|
14
|
+
|
|
15
|
+
resource_parent || (resource_record if controller.action_name.in? %w[show edit])
|
|
16
|
+
end
|
|
17
|
+
|
|
8
18
|
# The pages of the record the card is about, as `[label, path, current]` — a
|
|
9
19
|
# look first, a change second, then one tab per nested index: `8 ZIPs` where
|
|
10
20
|
# a counter cache answers, the bare `Settings` where none does. On a nested
|
|
@@ -58,7 +58,7 @@ module Recourse
|
|
|
58
58
|
# counter is headed with what it counts — `ZIPs`, not `ZIPs count` — since the
|
|
59
59
|
# column holds a number and the heading says what the number is of.
|
|
60
60
|
def resource_column_title(column)
|
|
61
|
-
counted = resource_model
|
|
61
|
+
counted = Recourse.counters(resource_model)[column]
|
|
62
62
|
return dated_title column unless counted
|
|
63
63
|
|
|
64
64
|
Recourse.model_title counted.klass
|
|
@@ -81,7 +81,7 @@ module Recourse
|
|
|
81
81
|
value = resource.attributes[column]
|
|
82
82
|
return blob_link resource, value if blob_filename? column
|
|
83
83
|
|
|
84
|
-
counted = resource_model
|
|
84
|
+
counted = Recourse.counters(resource_model)[column]
|
|
85
85
|
|
|
86
86
|
# A count is the bare number — the icon in the heading already says what it
|
|
87
87
|
# counts — linking to the counted rows where a block nested their index here.
|
|
@@ -8,7 +8,7 @@ module Recourse
|
|
|
8
8
|
# The class a counter's cells carry, which is what sizes the column like the
|
|
9
9
|
# action columns beside it rather than like the columns carrying text.
|
|
10
10
|
def counter_class(column)
|
|
11
|
-
'recourse-counter' if resource_model.
|
|
11
|
+
'recourse-counter' if Recourse.counters(resource_model).key? column
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
# A figure, and the word saying what it counts. The `aria-label` carries both,
|
|
@@ -43,7 +43,8 @@ module Recourse
|
|
|
43
43
|
word = Recourse.model_title association.klass, count: value, lower: true
|
|
44
44
|
|
|
45
45
|
safe_join [
|
|
46
|
-
tag.span(count, class: 'recourse-counter-figure',
|
|
46
|
+
tag.span(count, class: 'recourse-counter-figure',
|
|
47
|
+
data: tooltip_data(title, placement: 'top')),
|
|
47
48
|
tag.span("#{count} #{word}", class: 'recourse-counter-word'),
|
|
48
49
|
]
|
|
49
50
|
end
|
|
@@ -18,12 +18,10 @@ module Recourse
|
|
|
18
18
|
path = destroy_resource_path record
|
|
19
19
|
return unless path
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
button_to t('recourse.delete', model: resource_name), path, **options
|
|
21
|
+
confirm_button_to t('recourse.delete', model: resource_name), path,
|
|
22
|
+
confirm: destroy_warning(record), method: :delete,
|
|
23
|
+
class: 'btn btn-sm btn-solid theme-danger ms-3',
|
|
24
|
+
form_class: 'd-inline-block'
|
|
27
25
|
end
|
|
28
26
|
|
|
29
27
|
# What deleting this record takes with it, counted a level down and no further:
|
|
@@ -78,7 +78,7 @@ module Recourse
|
|
|
78
78
|
def filter_counter(klass)
|
|
79
79
|
return if resource_parent
|
|
80
80
|
|
|
81
|
-
klass.
|
|
81
|
+
Recourse.counters(klass).find { |_, one| one.klass == resource_model }&.first
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
# Never invalid and never required: a filter narrows rather than sets.
|
|
@@ -1,24 +1,16 @@
|
|
|
1
1
|
module Recourse
|
|
2
2
|
module Helpers
|
|
3
|
-
# What a flash message is drawn as:
|
|
4
|
-
# words it leads somewhere.
|
|
3
|
+
# What a flash message is drawn as: the row it marks, and the words it leads somewhere.
|
|
5
4
|
module Flashes
|
|
6
|
-
# Bootstrap theme for each flash key, so a notice and an alert read apart.
|
|
7
|
-
FLASH_THEMES = { 'notice' => 'theme-success', 'alert' => 'theme-danger' }
|
|
8
|
-
|
|
9
5
|
private
|
|
10
6
|
|
|
11
|
-
# Theme for one flash entry, falling back to a neutral one for a host's key.
|
|
12
|
-
def flash_theme(key)
|
|
13
|
-
FLASH_THEMES.fetch key.to_s, 'theme-primary'
|
|
14
|
-
end
|
|
15
|
-
|
|
16
7
|
# What marks the row a write just landed on, and nothing at all on a page no write
|
|
17
8
|
# brought about — which is every page but the one after a create or an update.
|
|
18
9
|
def written_data
|
|
19
10
|
written = flash[Recourse::WRITTEN]
|
|
11
|
+
return {} if written.blank?
|
|
20
12
|
|
|
21
|
-
{ controller: 'written', written_row_value: written['row'] }
|
|
13
|
+
{ controller: 'written', written_row_value: written['row'] }
|
|
22
14
|
end
|
|
23
15
|
|
|
24
16
|
# The message with the record it names led to that record's page, where a write
|
|
@@ -43,7 +43,7 @@ module Recourse
|
|
|
43
43
|
# already follow; and everything else is the type the attribute itself reports —
|
|
44
44
|
# `:monetary` included, where a host has registered a type that says so.
|
|
45
45
|
def attribute_kind(column)
|
|
46
|
-
return :counter if resource_model.
|
|
46
|
+
return :counter if Recourse.counters(resource_model).key? column
|
|
47
47
|
return :enum if resource_model.defined_enums.key? column
|
|
48
48
|
return :list if Recourse.list_column? resource_model, column
|
|
49
49
|
return :phone if column == 'phone'
|
|
@@ -20,13 +20,7 @@ module Recourse
|
|
|
20
20
|
# an icon alone says nothing to one, and given the tooltip that says the same
|
|
21
21
|
# word to everyone else.
|
|
22
22
|
def icon_heading(concept, title, **)
|
|
23
|
-
icon_tag concept, label: title, role: :img, data:
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def tooltip_on_top(title)
|
|
27
|
-
# `bs_title` is what Bootstrap's tooltip reads, and the controller is what
|
|
28
|
-
# makes one: Bootstrap never wires a tooltip on its own.
|
|
29
|
-
{ controller: 'tooltip', bs_placement: 'top', bs_title: title }
|
|
23
|
+
icon_tag concept, label: title, role: :img, data: tooltip_data(title, placement: 'top'), **
|
|
30
24
|
end
|
|
31
25
|
end
|
|
32
26
|
end
|
|
@@ -39,7 +39,7 @@ module Recourse
|
|
|
39
39
|
# showing the count it was cached with. Read off the rows in memory like the
|
|
40
40
|
# version is, so this costs no query either.
|
|
41
41
|
def counters_version(rows)
|
|
42
|
-
columns = resource_model.
|
|
42
|
+
columns = Recourse.counters(resource_model).keys
|
|
43
43
|
return if columns.empty?
|
|
44
44
|
|
|
45
45
|
rows.map { |row| row.attributes.values_at(*columns) }
|
|
@@ -44,13 +44,23 @@ module Recourse
|
|
|
44
44
|
end
|
|
45
45
|
|
|
46
46
|
# The model's search field, less the reach-through a nested route already
|
|
47
|
-
# answered: a page pinned to one provider offers no box to search them all.
|
|
47
|
+
# answered: a page pinned to one provider offers no box to search them all. A
|
|
48
|
+
# host naming a predicate of its own has said what it wants searched, and what
|
|
49
|
+
# it said is read on every page — which is what answering differently from the
|
|
50
|
+
# gem means here.
|
|
48
51
|
def resource_search_field
|
|
49
|
-
resource_model.search_field
|
|
52
|
+
chosen = resource_model.search_field
|
|
53
|
+
return chosen unless chosen == Recourse.search_field(resource_model)
|
|
54
|
+
|
|
55
|
+
Recourse.search_field resource_model, except: resource_parent_association
|
|
50
56
|
end
|
|
51
57
|
|
|
58
|
+
# And the words above it, narrowed the same way and for the same reason.
|
|
52
59
|
def resource_search_prompt
|
|
53
|
-
resource_model.search_prompt
|
|
60
|
+
chosen = resource_model.search_prompt
|
|
61
|
+
return chosen unless chosen == Recourse.search_prompt(resource_model)
|
|
62
|
+
|
|
63
|
+
Recourse.search_prompt resource_model, except: resource_parent_association
|
|
54
64
|
end
|
|
55
65
|
|
|
56
66
|
# A foreign key's cell shows a label from the other table, so what decides is
|
|
@@ -37,7 +37,7 @@ module Recourse
|
|
|
37
37
|
# and nobody types anything under it — and `Location address line 1` over a column
|
|
38
38
|
# of addresses reads as a form's question asked where there is no form.
|
|
39
39
|
def sort_title(column)
|
|
40
|
-
counted = resource_model
|
|
40
|
+
counted = Recourse.counters(resource_model)[column.to_s]
|
|
41
41
|
return counter_title counted if counted && @recourse_headers
|
|
42
42
|
|
|
43
43
|
resource_column_title column.to_s
|
|
@@ -14,7 +14,7 @@ module Recourse
|
|
|
14
14
|
association = nested_association record, name if action == :index
|
|
15
15
|
return association_tab_label record, association, namespace if association
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
tab_label_for Recourse.known_icon(name), routed_tab_name(name, namespace, action)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def nested_association(record, name)
|
|
@@ -36,15 +36,19 @@ module Recourse
|
|
|
36
36
|
[lead.presence&.upcase_first, title].compact.join ' '
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
# The icon is the counted model's own, whatever leads the words beside it.
|
|
40
|
-
# words are wrapped where a picture or a figure stands beside them, so a phone,
|
|
41
|
-
# which has room for neither, keeps the picture and the figure and drops the words.
|
|
42
|
-
# A figure and its words share one element: the link lays its children out with a
|
|
43
|
-
# gap, and two of them would stand a gap and a space apart.
|
|
39
|
+
# The icon is the counted model's own, whatever leads the words beside it.
|
|
44
40
|
def association_tab_label(record, association, namespace)
|
|
45
41
|
icon = Recourse.known_model_icon association.klass
|
|
46
42
|
count = tab_count record, association
|
|
47
|
-
|
|
43
|
+
|
|
44
|
+
tab_label_for icon, tab_words(association, namespace, count), count
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# The words with whatever stands beside them. The words are wrapped where a picture
|
|
48
|
+
# or a figure does, so a phone, which has room for neither, keeps the picture and the
|
|
49
|
+
# figure and drops the words. A figure and its words share one element: the link lays
|
|
50
|
+
# its children out with a gap, and two of them would stand a gap and a space apart.
|
|
51
|
+
def tab_label_for(icon, words, count = nil)
|
|
48
52
|
words = tag.span words, class: 'recourse-tab-word' if icon || count
|
|
49
53
|
words = tag.span safe_join([count, words], ' ') if count
|
|
50
54
|
|
|
@@ -96,7 +100,7 @@ module Recourse
|
|
|
96
100
|
end
|
|
97
101
|
|
|
98
102
|
def counter_of(model, association)
|
|
99
|
-
model.
|
|
103
|
+
Recourse.counters(model).find { |_, one| one == association }&.first if association
|
|
100
104
|
end
|
|
101
105
|
|
|
102
106
|
def join_of(model, association)
|
data/lib/recourse/recoursive.rb
CHANGED
|
@@ -27,18 +27,6 @@ module Recourse
|
|
|
27
27
|
# whichever way round it was written.
|
|
28
28
|
def recourse_displayed = []
|
|
29
29
|
|
|
30
|
-
# Columns holding a counter cache, each mapped to the association it counts. Read
|
|
31
|
-
# from the `belongs_to` on the other side, which is where `counter_cache` is
|
|
32
|
-
# declared: a column merely named `quote_count` is not one of these. Worked out
|
|
33
|
-
# once per class, the way `recourse_listable?` is: associations are declared at
|
|
34
|
-
# load and a reloaded class is a new one, with a memo of its own.
|
|
35
|
-
def recourse_counters
|
|
36
|
-
@recourse_counters ||= reflect_on_all_associations(:has_many).filter_map do |association|
|
|
37
|
-
column = association.inverse_of&.counter_cache_column
|
|
38
|
-
[column, association] if column
|
|
39
|
-
end.to_h
|
|
40
|
-
end
|
|
41
|
-
|
|
42
30
|
# `ZIP code`: what to call a foreign key pointing here. A form's label, a table's
|
|
43
31
|
# heading and a search prompt all name the same thing, so they name it once.
|
|
44
32
|
def recourse_reference_name
|
|
@@ -2,28 +2,14 @@ module Recourse
|
|
|
2
2
|
module Searchable
|
|
3
3
|
# What the search box above a table submits, and what it says while it is empty.
|
|
4
4
|
module Terms
|
|
5
|
-
# The predicate a search box submits: everything
|
|
6
|
-
# joined by `or`.
|
|
7
|
-
#
|
|
8
|
-
|
|
9
|
-
# pinned to one provider offers no box to search them all.
|
|
10
|
-
def search_field(except: nil)
|
|
11
|
-
fields, predicate = recourse_search_terms(except:)
|
|
12
|
-
return if fields.empty?
|
|
13
|
-
|
|
14
|
-
"#{fields.join '_or_'}_#{predicate}"
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
# What the search box says while it is empty, naming what it looks through.
|
|
18
|
-
def search_prompt(except: nil)
|
|
19
|
-
fields, predicate = recourse_search_terms(except:)
|
|
20
|
-
return if fields.empty?
|
|
5
|
+
# The predicate a search box submits: everything the model looks through at once,
|
|
6
|
+
# joined by `or`. A host naming its own instead is taken whole, on every page the
|
|
7
|
+
# model is read on.
|
|
8
|
+
def search_field = Recourse.search_field(self)
|
|
21
9
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
private
|
|
10
|
+
# What the search box says while it is empty, naming what it looks through. A
|
|
11
|
+
# host's own words are taken whole here too.
|
|
12
|
+
def search_prompt = Recourse.search_prompt(self)
|
|
27
13
|
|
|
28
14
|
# What a search box looks through, and how it matches: the plaintext columns
|
|
29
15
|
# and the labels behind foreign keys — less the one `except:` names — on
|
|
@@ -43,6 +29,8 @@ module Recourse
|
|
|
43
29
|
fields.map { |field| Recourse.downcase recourse_term_name(field) }
|
|
44
30
|
end
|
|
45
31
|
|
|
32
|
+
private
|
|
33
|
+
|
|
46
34
|
# A term is a column of this model, or a `zip_code` reaching through one of its
|
|
47
35
|
# foreign keys — which a form and a table already have a name for.
|
|
48
36
|
def recourse_term_name(field)
|
data/lib/recourse/searchable.rb
CHANGED
|
@@ -37,7 +37,7 @@ module Recourse
|
|
|
37
37
|
# for leaving those out too: hidden from every screen means hidden here.
|
|
38
38
|
def ransortable_attributes(_auth_object = nil)
|
|
39
39
|
readable = ransackable_attributes - recourse_encrypted_names
|
|
40
|
-
indexed = recourse_indexed_columns +
|
|
40
|
+
indexed = recourse_indexed_columns + Recourse.counters(self).keys + Recourse::TIMESTAMPS
|
|
41
41
|
|
|
42
42
|
keys = recourse_references.map { |one| one.foreign_key.to_s }
|
|
43
43
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# Reopened for what a search box submits and what it says, which a page narrows by the
|
|
2
|
+
# association its route has already answered.
|
|
3
|
+
module Recourse
|
|
4
|
+
# The predicate a search box submits: every term the model looks through, joined by
|
|
5
|
+
# `or`. Nil where a model has nothing worth looking through, which is what leaves its
|
|
6
|
+
# index without the form -- filters and all. `except:` drops the association a nested
|
|
7
|
+
# route already answered, so a page pinned to one provider offers no box to search
|
|
8
|
+
# them all.
|
|
9
|
+
# @param model [Class] the model the page is about.
|
|
10
|
+
# @param except [ActiveRecord::Reflection::AbstractReflection, nil] what the route said.
|
|
11
|
+
# @return [String, nil] the predicate, or nothing where there is none.
|
|
12
|
+
def self.search_field(model, except: nil)
|
|
13
|
+
fields, predicate = model.recourse_search_terms(except:)
|
|
14
|
+
return if fields.empty?
|
|
15
|
+
|
|
16
|
+
"#{fields.join '_or_'}_#{predicate}"
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# What the search box says while it is empty, naming what it looks through.
|
|
20
|
+
# @param model [Class] the model the page is about.
|
|
21
|
+
# @param except [ActiveRecord::Reflection::AbstractReflection, nil] what the route said.
|
|
22
|
+
# @return [String, nil] the words, or nothing where there is nothing to look through.
|
|
23
|
+
def self.search_prompt(model, except: nil)
|
|
24
|
+
fields, predicate = model.recourse_search_terms(except:)
|
|
25
|
+
return if fields.empty?
|
|
26
|
+
|
|
27
|
+
list = model.recourse_search_names(except:).join ' or '
|
|
28
|
+
|
|
29
|
+
I18n.t "recourse.searched_#{predicate}", list: list
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/recourse/version.rb
CHANGED
data/lib/recourse.rb
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
require 'bh'
|
|
1
2
|
require 'pagy'
|
|
2
3
|
# Before searchable.rb, so its `extend` lands ahead of Ransack's own defaults.
|
|
3
4
|
require 'ransack'
|
|
@@ -11,6 +12,8 @@ require_relative 'recourse/bookmarks'
|
|
|
11
12
|
require_relative 'recourse/calendars'
|
|
12
13
|
require_relative 'recourse/colors'
|
|
13
14
|
require_relative 'recourse/columns'
|
|
15
|
+
require_relative 'recourse/counters'
|
|
16
|
+
require_relative 'recourse/searches'
|
|
14
17
|
require_relative 'recourse/themes'
|
|
15
18
|
require_relative 'recourse/icons'
|
|
16
19
|
require_relative 'recourse/controllers'
|
|
@@ -58,7 +61,7 @@ module Recourse
|
|
|
58
61
|
# `create` permits these. A counter cache is none of a user's business — Rails keeps
|
|
59
62
|
# it, so a form that offered one would let it be typed over.
|
|
60
63
|
def self.editable_columns(model)
|
|
61
|
-
ordered model, model.column_names - ['id', *TIMESTAMPS] - model.
|
|
64
|
+
ordered model, model.column_names - ['id', *TIMESTAMPS] - counters(model).keys -
|
|
62
65
|
hidden_columns(model)
|
|
63
66
|
end
|
|
64
67
|
|
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
|
+
version: 7.0.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- claudiob
|
|
@@ -37,6 +37,20 @@ dependencies:
|
|
|
37
37
|
- - ">="
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '8.1'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: bh
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '6.7'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '6.7'
|
|
40
54
|
- !ruby/object:Gem::Dependency
|
|
41
55
|
name: pagy
|
|
42
56
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -144,7 +158,6 @@ files:
|
|
|
144
158
|
- app/views/recourses/_color.html.erb
|
|
145
159
|
- app/views/recourses/_combobox.html.erb
|
|
146
160
|
- app/views/recourses/_fields.html.erb
|
|
147
|
-
- app/views/recourses/_flash.html.erb
|
|
148
161
|
- app/views/recourses/_footer.html.erb
|
|
149
162
|
- app/views/recourses/_form.html.erb
|
|
150
163
|
- app/views/recourses/_map.html.erb
|
|
@@ -172,6 +185,7 @@ files:
|
|
|
172
185
|
- lib/recourse/colors.rb
|
|
173
186
|
- lib/recourse/columns.rb
|
|
174
187
|
- lib/recourse/controllers.rb
|
|
188
|
+
- lib/recourse/counters.rb
|
|
175
189
|
- lib/recourse/densities.rb
|
|
176
190
|
- lib/recourse/engine.rb
|
|
177
191
|
- lib/recourse/helpers.rb
|
|
@@ -242,6 +256,7 @@ files:
|
|
|
242
256
|
- lib/recourse/searchable/columns.rb
|
|
243
257
|
- lib/recourse/searchable/filters.rb
|
|
244
258
|
- lib/recourse/searchable/terms.rb
|
|
259
|
+
- lib/recourse/searches.rb
|
|
245
260
|
- lib/recourse/themes.rb
|
|
246
261
|
- lib/recourse/titles.rb
|
|
247
262
|
- lib/recourse/version.rb
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
<% if flash.any? -%>
|
|
2
|
-
<%# `data-turbo-temporary`: a toast born visible would otherwise replay from the
|
|
3
|
-
page snapshot on every Back, announcing a save that happened a page ago. -%>
|
|
4
|
-
<%# The row a write landed on rides here rather than on a toast: this exists once
|
|
5
|
-
per page, `hidden.bs.toast` bubbles up to it from whichever toast hides, and
|
|
6
|
-
`data-turbo-temporary` already takes the whole thing away on a Back. The same
|
|
7
|
-
key carries the record's words and its page, which the notice below links. -%>
|
|
8
|
-
<div class='toast-container position-fixed bottom-0 end-0 p-3' data-turbo-temporary
|
|
9
|
-
<%= tag.attributes data: written_data %>>
|
|
10
|
-
<%# Every key here becomes a toast, whoever invented it — so the one carrying the
|
|
11
|
-
written row, which is data rather than a message, is taken out first. -%>
|
|
12
|
-
<% flash.to_hash.except(Recourse::WRITTEN).each do |key, message| -%>
|
|
13
|
-
<%# `fade show` from the server: visible at first paint, before any JavaScript.
|
|
14
|
-
The `toast` controller owns the timed hide, and hovering or focusing holds
|
|
15
|
-
the toast open while someone reads it or aims for the X. -%>
|
|
16
|
-
<div class='toast fade show <%= flash_theme key %>' role='alert' aria-live='assertive' aria-atomic='true'
|
|
17
|
-
data-controller='toast'
|
|
18
|
-
data-action='mouseenter->toast#stopTimer mouseleave->toast#startTimer focusin->toast#stopTimer focusout->toast#startTimer'>
|
|
19
|
-
<div class='toast-header border-0'>
|
|
20
|
-
<span class='me-auto'><%= written_message key, message %></span>
|
|
21
|
-
<button type='button' class='btn-close' data-bs-dismiss='toast' aria-label='Close'></button>
|
|
22
|
-
</div>
|
|
23
|
-
<div class='toast-body d-none'></div>
|
|
24
|
-
</div>
|
|
25
|
-
<% end -%>
|
|
26
|
-
</div>
|
|
27
|
-
<% end -%>
|