recourse 4.6.5 → 4.7.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: cde158b6a70e0ab5fcabba26ecf536b2ec53965727b1a1534bd31ff71c581feb
4
- data.tar.gz: f0e4917f3d7a165fc8754a92b148d541e7496e043fbf155f73a2ea29c5f1cc3d
3
+ metadata.gz: ba211bbbb2cad39fb435db7af8c982733056778da14b2617ed01616d7b471643
4
+ data.tar.gz: 3b847c74cb62a88425a882a839fc6d264581ab3f7e8aed661f53e03b184a12ed
5
5
  SHA512:
6
- metadata.gz: 433bd7b0bb4f43bb6427b215be3a7585bba1c2256132f99a2b8f3bddb492689a64d30ff2b451c7fd55c563e5b3b3fc38867cb294a20752838b67e6be69cc043c
7
- data.tar.gz: 6854c771a18f8b0b48d7157bb1d3fd1d4247d1d890112c02217640c3182eaca7d276d1de27e68a4153e7813b577c3099ff60fd0711fae5bb244295318f0958c4
6
+ metadata.gz: 7ba31e2730714820fa039e61ba649fc91e3b944b01703ded5c4a006b52068b0b5dc8997f667481994adb4d4fb4370660b3d2eed5fa0bb882d8307a44b0bc82ab
7
+ data.tar.gz: 99f83b13ef7fb727d140dcb6fbd44a3abb160fd10b2bd21d441fa4cb609e985463b66e044047503acdb69375645c8dc408ab2559d4faa00d62416a8dea95a9a9
data/CHANGELOG.md CHANGED
@@ -5,6 +5,22 @@ 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.7.0 - 2026-09-11
9
+
10
+ * [Feature] The toast after a create or an update names the record and links it to its page
11
+
12
+ `Blue Crew was updated.` rather than `Team was updated.`, with the label led to the
13
+ record's show page where the routes drew one, so a reader can go and look the row over.
14
+ The words are the label the model picked, or the model's own name where a record says
15
+ nothing. A rejected write and a delete still name the model. A host asserting the old
16
+ wording in its own tests updates the sentence.
17
+
18
+ * [Feature] The first click on a heading sorts the table downward, and the second turns it back up
19
+
20
+ The newest, the most and the latest are what a reader clicks a heading to find, and
21
+ ascending put them on the last page. A host asserting a heading's `asc` link in its
22
+ own tests now reads `desc`.
23
+
8
24
  ## 4.6.5 - 2026-09-09
9
25
 
10
26
  * [Fix] A table opens at 15 rows a page rather than 20; the 100-row page stays
@@ -5,14 +5,24 @@ module Recourse
5
5
  module Landing
6
6
  private
7
7
 
8
- # What a write says once it has landed: the message, and the row it landed on where
9
- # one survives, for the page to mark while that message stands.
8
+ # What a write says once it has landed: the message, and the record it landed on
9
+ # where one survives, for the page to mark and lead to while that message stands.
10
10
  def wrote(message, record = nil)
11
11
  flash.notice = message
12
- flash[Recourse::WRITTEN] = Recourse.row_id record if record
12
+ flash[Recourse::WRITTEN] = written record if record
13
13
  redirect_to written_url, status: :see_other
14
14
  end
15
15
 
16
+ # What the page is told about that record: the row to mark, the words the message
17
+ # calls it by, and its own page where the routes drew one — nil where they did not,
18
+ # so the words stay words. String keys: the flash rides the session as JSON.
19
+ def written(record)
20
+ shown = Recourse.routed? controller_path, 'show'
21
+ url = url_for(action: :show, id: record, only_path: true) if shown
22
+
23
+ { 'row' => Recourse.row_id(record), 'label' => Recourse.record_title(record), 'url' => url }
24
+ end
25
+
16
26
  # What a rejected write does instead: the form again, with the message over it and
17
27
  # the errors beside the fields that earned them.
18
28
  def rejected(record, page, message)
@@ -36,12 +36,11 @@ module Recourse
36
36
  def create
37
37
  record = assign resource_class.new
38
38
  record.assign_attributes resource_params
39
- model = human_name
40
39
 
41
40
  if create_resource record
42
- wrote t('recourse.created', model: model), record
41
+ wrote t('recourse.created', record: Recourse.record_title(record)), record
43
42
  else
44
- rejected record, :new, t('recourse.created_error', model: model)
43
+ rejected record, :new, t('recourse.created_error', model: human_name)
45
44
  end
46
45
  end
47
46
 
@@ -54,7 +53,7 @@ module Recourse
54
53
  # Saves changes to a record, then shows the index again or says what turned it down.
55
54
  def update
56
55
  if update_resource @recourse
57
- wrote t('recourse.updated', model: human_name), @recourse
56
+ wrote t('recourse.updated', record: Recourse.record_title(@recourse)), @recourse
58
57
  else
59
58
  rejected @recourse, :edit, t('recourse.updated_error', model: human_name)
60
59
  end
@@ -3,7 +3,8 @@
3
3
  page snapshot on every Back, announcing a save that happened a page ago. -%>
4
4
  <%# The row a write landed on rides here rather than on a toast: this exists once
5
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. -%>
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. -%>
7
8
  <div class='toast-container position-fixed bottom-0 end-0 p-3' data-turbo-temporary
8
9
  <%= tag.attributes data: written_data %>>
9
10
  <%# Every key here becomes a toast, whoever invented it — so the one carrying the
@@ -16,7 +17,7 @@
16
17
  data-controller='toast'
17
18
  data-action='mouseenter->toast#stopTimer mouseleave->toast#startTimer focusin->toast#stopTimer focusout->toast#startTimer'>
18
19
  <div class='toast-header border-0'>
19
- <span class='me-auto'><%= message %></span>
20
+ <span class='me-auto'><%= written_message key, message %></span>
20
21
  <button type='button' class='btn-close' data-bs-dismiss='toast' aria-label='Close'></button>
21
22
  </div>
22
23
  <div class='toast-body d-none'></div>
@@ -20,7 +20,7 @@ en:
20
20
  clear: Clear search
21
21
  compact: Compact
22
22
  create: Create
23
- created: "%{model} was created."
23
+ created: "%{record} was created."
24
24
  created_error: "%{model} could not be created."
25
25
  darken: Dark
26
26
  delete: Delete %{model}
@@ -65,7 +65,7 @@ en:
65
65
  blue. It cannot be %{color}."
66
66
  unknown_theme: "`Recourse.theme` is one of %{themes}, or nil for Bootstrap's own
67
67
  palette. It cannot be %{theme}."
68
- updated: "%{model} was updated."
68
+ updated: "%{record} was updated."
69
69
  updated_error: "%{model} could not be updated."
70
70
  unset: None
71
71
  deletion:
@@ -0,0 +1,36 @@
1
+ module Recourse
2
+ module Helpers
3
+ # What a flash message is drawn as: its toast's theme, the row it marks, and the
4
+ # words it leads somewhere.
5
+ 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
+ private
10
+
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
+ # What marks the row a write just landed on, and nothing at all on a page no write
17
+ # brought about — which is every page but the one after a create or an update.
18
+ def written_data
19
+ written = flash[Recourse::WRITTEN]
20
+
21
+ { controller: 'written', written_row_value: written['row'] } if written.present?
22
+ end
23
+
24
+ # The message with the record it names led to that record's page, where a write
25
+ # landed on one the routes can show. Spliced around the label rather than rebuilt,
26
+ # so the flash keeps the plain sentence for whoever else reads it.
27
+ def written_message(key, message)
28
+ written = flash[Recourse::WRITTEN]
29
+ return message unless key.to_s == 'notice' && written&.dig('url')
30
+
31
+ before, label, after = message.partition written['label']
32
+ safe_join [before, label.presence && link_to(label, written['url']), after]
33
+ end
34
+ end
35
+ end
36
+ end
@@ -58,15 +58,9 @@ module Recourse
58
58
  end
59
59
 
60
60
  def parent_title(parent)
61
- # A label is not always words: a numeric one is spoken as a string —
62
- # `truncate` has no patience for an Integer — and where a record says
63
- # nothing at all, the model's own name stands in, as the delete warning's
64
- # title already does.
65
- label = parent.attributes[parent.class.recourse_label.to_s]
66
-
67
- # Forty characters of the label, no more: a record named by an address or a
61
+ # Forty characters of the title, no more: a record named by an address or a
68
62
  # sentence would otherwise walk the crumb into the navbar's search form.
69
- truncate label.presence&.to_s || parent.class.model_name.human, length: 40
63
+ truncate Recourse.record_title(parent), length: 40
70
64
  end
71
65
 
72
66
  # One of the parent's own pages, or nil where the host drew no route to it —
@@ -10,13 +10,17 @@ module Recourse
10
10
  # allows that, and the plain title everywhere else. Only the header row draws
11
11
  # the link, so the `data-cell` on every other row stays readable text.
12
12
  #
13
+ # The first click sorts descending, the second back up: the newest, the most
14
+ # and the latest are what a reader clicks a heading to find.
15
+ #
13
16
  # Named apart from Ransack's `sort_link`, which it calls: sharing the name
14
17
  # would take that helper away from every view this gem's controllers render.
15
18
  def sort_header(column, title = nil)
16
19
  title ||= sort_title column
17
20
  return title unless @recourse_headers && sortable_column?(column)
18
21
 
19
- sort_link resource_search, column.to_sym, hide_indicator: true, page: nil do
22
+ sort_link resource_search, column.to_sym,
23
+ hide_indicator: true, page: nil, default_order: :desc do
20
24
  safe_join [title, sort_caret(column)].compact, ' '
21
25
  end
22
26
  end
@@ -14,6 +14,7 @@ require_relative 'helpers/details'
14
14
  require_relative 'helpers/examples'
15
15
  require_relative 'helpers/fields'
16
16
  require_relative 'helpers/filters'
17
+ require_relative 'helpers/flashes'
17
18
  require_relative 'helpers/formats'
18
19
  require_relative 'helpers/inputs'
19
20
  require_relative 'helpers/kinds'
@@ -43,7 +44,7 @@ module Recourse
43
44
  include Actions, Bookmarks, Buttons, Cards,
44
45
  Cells, Choices, Colors, Comboboxes, Constraints, Counters, Deletions, Densities,
45
46
  Details,
46
- Examples, Fields, Filters, Formats, Inputs, Kinds, Limits,
47
+ Examples, Fields, Filters, Flashes, Formats, Inputs, Kinds, Limits,
47
48
  Names, Navigation, Parents, Pictures, References, Refreshes,
48
49
  Routing,
49
50
  Resources, Rows, Searches, Shortcuts, Sidebars, Sorts, Tabs, Themes,
@@ -55,24 +56,8 @@ module Recourse
55
56
  # show page alone — `.recourse-values` in the layout draws it.
56
57
  ROW = 'recourse-row pb-2 mb-3 lg:col-6'
57
58
 
58
- # Bootstrap theme for each flash key, so a notice and an alert read apart.
59
- FLASH_THEMES = { 'notice' => 'theme-success', 'alert' => 'theme-danger' }
60
-
61
- # Theme for one flash entry, falling back to a neutral one for a host's key.
62
- def flash_theme(key)
63
- FLASH_THEMES.fetch key.to_s, 'theme-primary'
64
- end
65
-
66
59
  private
67
60
 
68
- # What marks the row a write just landed on, and nothing at all on a page no write
69
- # brought about — which is every page but the one after a create or an update.
70
- def written_data
71
- row = flash[Recourse::WRITTEN]
72
-
73
- { controller: 'written', written_row_value: row } if row.present?
74
- end
75
-
76
61
  # `?q=anything` arrives as a String, which has no parameters to read — the
77
62
  # same test `Recourse::Search` makes, spelled the same way.
78
63
  def query_params
@@ -50,6 +50,14 @@ module Recourse
50
50
 
51
51
  lower ? downcase(title) : title
52
52
  end
53
+
54
+ # What one record is called: its label, spoken as a string — a numeric one is not
55
+ # words — or the model's own name where it says nothing at all.
56
+ def record_title(record)
57
+ label = record.attributes[record.class.recourse_label.to_s]
58
+
59
+ label.presence&.to_s || record.class.model_name.human
60
+ end
53
61
  end
54
62
 
55
63
  extend Titles
@@ -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.6.5'
3
+ VERSION = '4.7.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.6.5
4
+ version: 4.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - claudiob
@@ -208,6 +208,7 @@ files:
208
208
  - lib/recourse/helpers/examples.rb
209
209
  - lib/recourse/helpers/fields.rb
210
210
  - lib/recourse/helpers/filters.rb
211
+ - lib/recourse/helpers/flashes.rb
211
212
  - lib/recourse/helpers/formats.rb
212
213
  - lib/recourse/helpers/inputs.rb
213
214
  - lib/recourse/helpers/kinds.rb