recourse 7.4.0 → 7.6.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: da821317fa8c02a37911dd03b62aaeb072bb3cacbc40cfb15c29eb51c3ffbd8b
4
- data.tar.gz: 276e8d9570b080d81dbefc6b304ba6d4a169e783191587cbe534059a2ccd3c0b
3
+ metadata.gz: a2a20bbc5915a1d57b72fb203edfdd05c1bd5f994c428b0520f2ef9af73ec3f0
4
+ data.tar.gz: a6ba069086c36351dd7072d09a2909f64bd17a84346d8ef31921fd953592a61d
5
5
  SHA512:
6
- metadata.gz: 172bd06e4fc69264846c29ed4c12123290aab2532c98033076c1fce4f0ab7829931cfeac6d16e16765c7d25117eec969f624b2fd3a66120878d85fc3f56f57ee
7
- data.tar.gz: f7ab6fd22033642867259dc840ec3da125c9914c08af2a9617a72ebf67fa4cf7e70c7b5930f928ef8bac45855449111621ef4565b323da2631958e5a9af70392
6
+ metadata.gz: c98284ac83b4a7b5419f747e69028dff262aa5281745c61bc8fbd01d7dd0eeb07dc4706ffbbb531133c8c8bc1b2478e75b55a12484cb39a6ca2b539df2821ea5
7
+ data.tar.gz: cdede705768f5b68438efd3d1e80f1120827614a16d8bf11b9556aad5107184f4691d18e893a3d2ee8e1223a1ceef92f74c9026c60d278c6e3d904aa332edae9
data/CHANGELOG.md CHANGED
@@ -7,6 +7,28 @@ For more information about changelogs, check [Keep a Changelog](http://keepachan
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## 7.6.0 - 2026-09-20
11
+
12
+ * [CHANGE] The layout titles the page, so a host template cannot lose it
13
+
14
+ The four templates each set `content_for :title`, so a host writing one of its own —
15
+ which is the whole point of the gem drawing the rest — left the tab reading `Recourse`.
16
+ The title is chrome like the trail beside it, and the layout works it out from the same
17
+ place: what the trail ends with, the resource it is of where the record names nothing,
18
+ and the app's own name where neither answers. `content_for :title` still wins where a
19
+ host sets one.
20
+
21
+ ## 7.5.0 - 2026-09-20
22
+
23
+ * [FEATURE] A model may word its own deletion
24
+
25
+ Deleting is not what every model does when a row goes: an account is disconnected, a
26
+ note is withdrawn. Both the button and the heading over the dialog now read
27
+ `recourse.models.<model>.delete` and `recourse.models.<model>.deletion_title` where a
28
+ host wrote them, and the one word the gem has where nobody did. Keyed off the record's
29
+ own class, so a subclass words it apart from its siblings — and a slash in an i18n key
30
+ is a nesting, so `integration/jobber` sits under `integration` in the locale.
31
+
10
32
  ## 7.4.0 - 2026-09-20
11
33
 
12
34
  * [CHANGE] A singular resource that has its record reads it rather than offering a form
@@ -2,7 +2,7 @@
2
2
  <html lang='en'>
3
3
  <head>
4
4
  <meta charset='utf-8'>
5
- <title><%= content_for(:title) || 'Recourse' %></title>
5
+ <title><%= content_for(:title) || page_title %></title>
6
6
  <meta name='viewport' content='width=device-width, initial-scale=1'>
7
7
  <meta name='color-scheme' content='light dark'>
8
8
  <%# A refresh Turbo is sent morphs the page in place and keeps the scroll; without these it
@@ -1,3 +1 @@
1
- <% content_for :title, resource_record_label %>
2
-
3
1
  <%= render 'form', resource_key => resource_record %>
@@ -1,5 +1,3 @@
1
- <% content_for :title, resources_name %>
2
-
3
1
  <% if (new_path = new_resource_path) %>
4
2
  <% content_for :actions do %>
5
3
  <%= link_to t('recourse.add', model: resource_name), new_path,
@@ -1,3 +1 @@
1
- <% content_for :title, t('recourse.new', model: resource_name) %>
2
-
3
1
  <%= render 'form', resource_key => resource_record %>
@@ -1,5 +1,3 @@
1
- <% content_for :title, resource_record_label %>
2
-
3
1
  <%# Nothing to read out where the host found no record: a singular resource is reached
4
2
  with no id, so the one it stands for is one it may not have yet, and the page says so
5
3
  rather than reading attributes off nothing. -%>
@@ -46,6 +46,14 @@ module Recourse
46
46
  safe_join [tag.i(class: "bi bi-#{icon}"), word], ' '
47
47
  end
48
48
 
49
+ # What the tab calls this page: what the trail ends with, the resource it is of
50
+ # where the record names nothing, and the app's own name where neither answers.
51
+ def page_title
52
+ breadcrumb_leaf.presence || breadcrumb_name.presence || app_name
53
+ end
54
+
55
+ def app_name = Rails.application.class.module_parent_name
56
+
49
57
  # Only a page beneath the index names itself, and names what it is showing.
50
58
  def breadcrumb_leaf
51
59
  case controller.action_name
@@ -3,13 +3,6 @@ module Recourse
3
3
  # The button that deletes the record a form is showing, and the warning it puts
4
4
  # in front of whoever clicked it.
5
5
  module Deletions
6
- # `dependent:` values that take the children with the parent.
7
- DESTROYED = %i[destroy destroy_async].freeze
8
-
9
- # And the one that keeps them, holding the key open. Anything else — a bare
10
- # `has_many`, a `:restrict` — is left unsaid rather than guessed at.
11
- NULLIFIED = %i[nullify].freeze
12
-
13
6
  private
14
7
 
15
8
  # The record a page may delete: the one it is about, on its own look or its own
@@ -26,68 +19,37 @@ module Recourse
26
19
  path = record && destroy_resource_path(record)
27
20
  return unless path
28
21
 
29
- confirm_button_to t('recourse.delete', model: resource_name), path,
22
+ confirm_button_to destroy_label(record), path,
30
23
  confirm: destroy_warning(record), method: :delete,
31
24
  class: 'btn btn-sm btn-solid theme-danger ms-3',
32
25
  form_class: 'd-inline-block'
33
26
  end
34
27
 
35
- # @api private
36
- # What deleting this record takes with it, counted a level down and no further:
37
- # a state reaches counties, then ZIPs, then locations, and counting that far
38
- # would join 40,965 rows to draw one page.
39
- def destroy_warning(record)
40
- lines = [t('recourse.deletion.title', record: destroy_title(record)), nil]
41
-
42
- [*lines, *dependent_lines(record), nil, t('recourse.deletion.undone')].join "\n"
43
- end
44
-
45
- def destroy_resource_path(record)
46
- return unless routed_action? 'destroy'
47
-
48
- url_for action: :destroy, id: record
49
- end
50
-
51
- # The middle of the warning, in the order it reads: what goes, what stays, and
52
- # only then what is under what goes — the levels this stops short of counting.
53
- def dependent_lines(record)
54
- going = dependents record, DESTROYED
55
- staying = dependents record, NULLIFIED
56
- lines = []
57
- lines << t('recourse.deletion.going', list: going.to_sentence) if going.any?
58
- lines << staying_line(staying) if staying.any?
59
- lines << t('recourse.deletion.under') if going.any?
60
-
61
- lines
28
+ # The word on the button, and below it the word over the dialog. A model that
29
+ # undoes something rather than deleting it says so under its own name, and every
30
+ # other model falls through to the one word the gem has. Keyed off the record's
31
+ # class rather than the resource's, so a subclass words it apart from its
32
+ # siblings: an integration a provider authorized is disconnected where the one an
33
+ # admin picked is given up.
34
+ def destroy_label(record)
35
+ t worded(record, :delete), model: resource_name, default: :'recourse.delete'
62
36
  end
63
37
 
64
- def staying_line(staying)
65
- t 'recourse.deletion.staying', list: staying.to_sentence, model: resource_name
38
+ def destroy_heading(record)
39
+ t worded(record, :deletion_title), record: destroy_title(record),
40
+ default: :'recourse.deletion.title'
66
41
  end
67
42
 
68
- def dependents(record, kinds)
69
- record.class.reflect_on_all_associations(:has_many).filter_map do |association|
70
- next if association.through_reflection || kinds.exclude?(association.options[:dependent])
71
-
72
- dependent_count record, association
73
- end
43
+ # A slash in the key is a nesting to i18n, which is what lets a subclass sit
44
+ # under the model it inherits from rather than beside it.
45
+ def worded(record, name)
46
+ :"recourse.models.#{record.model_name.i18n_key}.#{name}"
74
47
  end
75
48
 
76
- # `association.reader` rather than a method named at runtime, and `count` rather
77
- # than loading them: the warning needs how many, never which.
78
- def dependent_count(record, association)
79
- count = record.association(association.name).reader.count
80
- return if count.zero?
81
-
82
- name = Recourse.downcase association.klass.model_name.human
83
-
84
- "#{number_with_delimiter count} #{name.pluralize count}"
85
- end
49
+ def destroy_resource_path(record)
50
+ return unless routed_action? 'destroy'
86
51
 
87
- # What the record is called, or what it is, for one that answers to no label.
88
- def destroy_title(record)
89
- record.attributes[record.class.recourse_label.to_s].presence ||
90
- record.class.model_name.human
52
+ url_for action: :destroy, id: record
91
53
  end
92
54
  end
93
55
  end
@@ -0,0 +1,68 @@
1
+ module Recourse
2
+ module Helpers
3
+ # What a deletion is said to cost before it is made: the heading over the dialog,
4
+ # and what the row takes with it or leaves behind.
5
+ module Warnings
6
+ # `dependent:` values that take the children with the parent.
7
+ DESTROYED = %i[destroy destroy_async].freeze
8
+
9
+ # And the one that keeps them, holding the key open. Anything else — a bare
10
+ # `has_many`, a `:restrict` — is left unsaid rather than guessed at.
11
+ NULLIFIED = %i[nullify].freeze
12
+
13
+ private
14
+
15
+ # @api private
16
+ # What deleting this record takes with it, counted a level down and no further:
17
+ # a state reaches counties, then ZIPs, then locations, and counting that far
18
+ # would join 40,965 rows to draw one page.
19
+ def destroy_warning(record)
20
+ lines = [destroy_heading(record), nil]
21
+
22
+ [*lines, *dependent_lines(record), nil, t('recourse.deletion.undone')].join "\n"
23
+ end
24
+
25
+ # The middle of the warning, in the order it reads: what goes, what stays, and
26
+ # only then what is under what goes — the levels this stops short of counting.
27
+ def dependent_lines(record)
28
+ going = dependents record, DESTROYED
29
+ staying = dependents record, NULLIFIED
30
+ lines = []
31
+ lines << t('recourse.deletion.going', list: going.to_sentence) if going.any?
32
+ lines << staying_line(staying) if staying.any?
33
+ lines << t('recourse.deletion.under') if going.any?
34
+
35
+ lines
36
+ end
37
+
38
+ def staying_line(staying)
39
+ t 'recourse.deletion.staying', list: staying.to_sentence, model: resource_name
40
+ end
41
+
42
+ def dependents(record, kinds)
43
+ record.class.reflect_on_all_associations(:has_many).filter_map do |association|
44
+ next if association.through_reflection || kinds.exclude?(association.options[:dependent])
45
+
46
+ dependent_count record, association
47
+ end
48
+ end
49
+
50
+ # `association.reader` rather than a method named at runtime, and `count` rather
51
+ # than loading them: the warning needs how many, never which.
52
+ def dependent_count(record, association)
53
+ count = record.association(association.name).reader.count
54
+ return if count.zero?
55
+
56
+ name = Recourse.downcase association.klass.model_name.human
57
+
58
+ "#{number_with_delimiter count} #{name.pluralize count}"
59
+ end
60
+
61
+ # What the record is called, or what it is, for one that answers to no label.
62
+ def destroy_title(record)
63
+ record.attributes[record.class.recourse_label.to_s].presence ||
64
+ record.class.model_name.human
65
+ end
66
+ end
67
+ end
68
+ end
@@ -47,6 +47,7 @@ require_relative 'helpers/spans'
47
47
  require_relative 'helpers/tabs'
48
48
  require_relative 'helpers/times'
49
49
  require_relative 'helpers/values'
50
+ require_relative 'helpers/warnings'
50
51
  require_relative 'helpers/weeks'
51
52
  require_relative 'helpers/zones'
52
53
 
@@ -58,8 +59,8 @@ module Recourse
58
59
  Deletions, Densities, Details, Events, Examples, Fields, Filters, Flashes,
59
60
  Formats, Inputs, Kinds, Limits, Links, Maps, Names, Navigation, Parents,
60
61
  Pictures, Previews, References, Refreshes, Routing, Resources, Rows, Searches,
61
- Shapes, Shortcuts, Sidebars, Sorts, Spans, Tabs, Themes, Times, Values, Weeks,
62
- Zones
62
+ Shapes, Shortcuts, Sidebars, Sorts, Spans, Tabs, Themes, Times, Values,
63
+ Warnings, Weeks, Zones
63
64
 
64
65
  # The grid a record's own two pages lay an attribute out in: two columns on a large
65
66
  # viewport, and the same padding on both, so a value and the field that edits it sit
@@ -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 = '7.4.0'
3
+ VERSION = '7.6.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: 7.4.0
4
+ version: 7.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - claudiob
@@ -240,6 +240,7 @@ files:
240
240
  - lib/recourse/helpers/themes.rb
241
241
  - lib/recourse/helpers/times.rb
242
242
  - lib/recourse/helpers/values.rb
243
+ - lib/recourse/helpers/warnings.rb
243
244
  - lib/recourse/helpers/weeks.rb
244
245
  - lib/recourse/helpers/zones.rb
245
246
  - lib/recourse/icons.rb