studio-engine 0.79.0 → 0.80.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 +13 -0
- data/README.md +30 -0
- data/app/views/components/_user_nav.html.erb +6 -2
- data/app/views/layouts/_navbar.html.erb +1 -1
- data/lib/studio/navbar_identity.rb +91 -0
- data/lib/studio/version.rb +1 -1
- data/lib/studio.rb +33 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7b9678d8f3c7e2fbb15531909b721ff80ccd9e933d15c283e0c7fa1861137b11
|
|
4
|
+
data.tar.gz: c495551acf3d80d22f1c7760d65cabb0f949021680f94f152514e409c117400a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 9c28aa14a0b3a28ca0dd876fc0835431f66588159ec3d11e79f6319f9350be50e4a49d4e6891253334a07f3a0c49db0974e74819b2d2d6f40a17460f5b8e68f9
|
|
7
|
+
data.tar.gz: 1221cfc40625adeda5fda0293de168969e95d50e43ecb01f2dde1c21c2d0b0f41807b10118f63f291ca74c094f7b1eec1ae23dc4b107be6e6543de8c5b2274ca
|
data/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,19 @@ The format is [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). This pro
|
|
|
4
4
|
|
|
5
5
|
## Unreleased
|
|
6
6
|
|
|
7
|
+
## 0.80.0 — 2026-09-30
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`Studio.navbar_user_name` and `Studio.sign_in_label` set the navbar's two
|
|
12
|
+
identity words.** `navbar_user_name` takes a method name (`:player_name`) or a
|
|
13
|
+
callable `->(user, view)` and replaces `display_name` in the signed-in user
|
|
14
|
+
nav; a blank answer or a raise falls back to `display_name`, and a raise is
|
|
15
|
+
reported once rather than 500ing the layout. `sign_in_label` (default
|
|
16
|
+
`"Log in"`) labels the signed-out button in `layouts/_navbar` and
|
|
17
|
+
`components/_user_nav`. An app that sets neither renders the same navbar bytes
|
|
18
|
+
as before. See the README, *Navbar identity*.
|
|
19
|
+
|
|
7
20
|
## 0.79.0 — 2026-09-30
|
|
8
21
|
|
|
9
22
|
### Changed
|
data/README.md
CHANGED
|
@@ -611,6 +611,36 @@ end
|
|
|
611
611
|
- A bad entry raises `Studio::NavbarLinks::InvalidLink` naming its index: at
|
|
612
612
|
assignment for a static Array, at render for a callable.
|
|
613
613
|
|
|
614
|
+
### Navbar identity — `Studio.navbar_user_name` and `Studio.sign_in_label`
|
|
615
|
+
|
|
616
|
+
Two words the navbar says about the viewer. Both defaults render the navbar
|
|
617
|
+
byte-identical to before, so an app that sets neither sees no change. Rules:
|
|
618
|
+
`lib/studio/navbar_identity.rb`.
|
|
619
|
+
|
|
620
|
+
```ruby
|
|
621
|
+
Studio.configure do |config|
|
|
622
|
+
# The signed-in name in components/_user_nav. nil (the default) is display_name.
|
|
623
|
+
config.navbar_user_name = :player_name # a method on the user
|
|
624
|
+
# config.navbar_user_name = ->(user, view) { user.player_name } # or a callable
|
|
625
|
+
|
|
626
|
+
# The signed-out button in layouts/_navbar and components/_user_nav.
|
|
627
|
+
config.sign_in_label = "Sign in" # default "Log in"
|
|
628
|
+
end
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
- `navbar_user_name` is `nil`, a method name (Symbol or String), or a callable.
|
|
632
|
+
A two-argument callable gets `(user, view)`; a one-argument callable gets the
|
|
633
|
+
user alone.
|
|
634
|
+
- The name is always escaped, even when the method or callable returns an
|
|
635
|
+
`html_safe` string.
|
|
636
|
+
- A method or callable that answers blank falls back to `display_name`. One
|
|
637
|
+
that raises also falls back, and the error is reported once per process per
|
|
638
|
+
error class (to `ErrorLog` when the app has it, else the Rails log), so a
|
|
639
|
+
broken setting never 500s the layout.
|
|
640
|
+
- The engine has no I18n catalogue, so the label is plain config, not a locale
|
|
641
|
+
key. A blank or non-String label, or a `navbar_user_name` of any other type,
|
|
642
|
+
raises `Studio::NavbarIdentity::InvalidConfig` at assignment.
|
|
643
|
+
|
|
614
644
|
### User nav slots
|
|
615
645
|
|
|
616
646
|
`components/_user_nav.html.erb` renders the right-side navbar user section.
|
|
@@ -11,6 +11,10 @@
|
|
|
11
11
|
address + level progress bar) entirely
|
|
12
12
|
(e.g. a seeds bar)
|
|
13
13
|
|
|
14
|
+
Config, not locals: the signed-in name is Studio.navbar_user_name
|
|
15
|
+
(default display_name) and the signed-out button reads
|
|
16
|
+
Studio.sign_in_label (default "Log in"); lib/studio/navbar_identity.rb.
|
|
17
|
+
|
|
14
18
|
Other locals:
|
|
15
19
|
show_logout_link — boolean, show explicit "Log out" link in the
|
|
16
20
|
second row (Studio uses this)
|
|
@@ -204,7 +208,7 @@
|
|
|
204
208
|
the link's accessible name. %>
|
|
205
209
|
<% account_classes = "group flex items-center gap-2 flex-shrink-0" %>
|
|
206
210
|
<% account_inner = capture do %>
|
|
207
|
-
<span class="sr-only md:not-sr-only md:max-w-40"><span class="block truncate text-right text-heading font-semibold text-base leading-none transition<%= " group-hover:text-primary" if studio_nav_destination %>" data-nav-name><%= current_user
|
|
211
|
+
<span class="sr-only md:not-sr-only md:max-w-40"><span class="block truncate text-right text-heading font-semibold text-base leading-none transition<%= " group-hover:text-primary" if studio_nav_destination %>" data-nav-name><%= Studio.navbar_user_name_for(current_user, self) %></span></span>
|
|
208
212
|
<span class="flex-shrink-0 flex items-center" aria-hidden="true">
|
|
209
213
|
<%= render "components/avatar", user: current_user, size: "nav" %>
|
|
210
214
|
</span>
|
|
@@ -219,7 +223,7 @@
|
|
|
219
223
|
<%# Desktop only, as above: the phone row carries the toggle below md. %>
|
|
220
224
|
<span class="hidden md:flex" data-nav-theme-toggle="desktop"><%= render "components/theme_toggle_morph" %></span>
|
|
221
225
|
<div class="flex items-center gap-3 ml-auto">
|
|
222
|
-
<%= link_to
|
|
226
|
+
<%= link_to Studio.sign_in_label, login_path, class: "text-heading hover:text-primary text-sm font-semibold transition" %>
|
|
223
227
|
<%= link_to "Sign up", signup_path, class: "btn btn-primary btn-sm" %>
|
|
224
228
|
</div>
|
|
225
229
|
<% end %>
|
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
<div class="flex items-center justify-end gap-3">
|
|
163
163
|
<%= sidebar_trigger_desktop if sidebar_active %>
|
|
164
164
|
<span class="hidden md:flex"><%= render "components/theme_toggle_morph" %></span>
|
|
165
|
-
<%= link_to
|
|
165
|
+
<%= link_to Studio.sign_in_label, login_path, class: "btn btn-primary" %>
|
|
166
166
|
</div>
|
|
167
167
|
<% end %>
|
|
168
168
|
</div>
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Resolves the two words the engine navbar says about who is (or is not) signed
|
|
4
|
+
# in: the signed-in user's name (Studio.navbar_user_name) and the signed-out
|
|
5
|
+
# button's label (Studio.sign_in_label). Pure Ruby, so the unit suite covers it
|
|
6
|
+
# without the dummy app.
|
|
7
|
+
#
|
|
8
|
+
# Studio.navbar_user_name is one of:
|
|
9
|
+
#
|
|
10
|
+
# nil the default: user.display_name, as before
|
|
11
|
+
# :player_name a method called on the user ("player_name" works too)
|
|
12
|
+
# ->(user, view) { ... } a callable; a one-argument callable gets the user only
|
|
13
|
+
#
|
|
14
|
+
# A layout must not 500 over a name. When the configured method or callable
|
|
15
|
+
# RAISES, the navbar prints display_name and the failure is reported once per
|
|
16
|
+
# process per error class (ErrorLog when the host has it, else Rails.logger),
|
|
17
|
+
# not once per page view. A BLANK answer falls back to display_name quietly:
|
|
18
|
+
# a user with no player name yet is data, not a fault.
|
|
19
|
+
#
|
|
20
|
+
# The name comes back as a plain String, never an html_safe buffer, so ERB
|
|
21
|
+
# escapes whatever a host's method or callable returned.
|
|
22
|
+
module Studio
|
|
23
|
+
module NavbarIdentity
|
|
24
|
+
class InvalidConfig < ArgumentError; end
|
|
25
|
+
|
|
26
|
+
DEFAULT_SIGN_IN_LABEL = "Log in"
|
|
27
|
+
|
|
28
|
+
@reported = {}
|
|
29
|
+
@reported_lock = Mutex.new
|
|
30
|
+
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
def user_name(user, view = nil, config: Studio.navbar_user_name)
|
|
34
|
+
fallback = user.display_name # outside the rescue: the default path is not ours to swallow
|
|
35
|
+
return fallback if config.nil?
|
|
36
|
+
|
|
37
|
+
begin
|
|
38
|
+
text = plain_text(configured_name(config, user, view))
|
|
39
|
+
rescue StandardError => e
|
|
40
|
+
report_once(e)
|
|
41
|
+
return fallback
|
|
42
|
+
end
|
|
43
|
+
text.empty? ? fallback : text
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def validate_name!(config)
|
|
47
|
+
return nil if config.nil? || config.respond_to?(:call)
|
|
48
|
+
return nil if (config.is_a?(Symbol) || config.is_a?(String)) && !config.to_s.strip.empty?
|
|
49
|
+
|
|
50
|
+
raise InvalidConfig, "Studio.navbar_user_name must be nil, a method name (Symbol) or a callable " \
|
|
51
|
+
"->(user, view) (got #{config.inspect})"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def validate_label!(label)
|
|
55
|
+
return nil if label.is_a?(String) && !label.strip.empty?
|
|
56
|
+
|
|
57
|
+
raise InvalidConfig, "Studio.sign_in_label must be non-blank text (got #{label.inspect})"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Forget what was reported, so a reconfigured name reports its own failure.
|
|
61
|
+
def reset_reported!
|
|
62
|
+
@reported_lock.synchronize { @reported.clear }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def configured_name(config, user, view)
|
|
66
|
+
return user.public_send(config.to_sym) unless config.respond_to?(:call)
|
|
67
|
+
|
|
68
|
+
config.respond_to?(:arity) && config.arity == 1 ? config.call(user) : config.call(user, view)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# String.new drops an html_safe flag: SafeBuffer#to_s returns the buffer
|
|
72
|
+
# itself, which ERB would print raw.
|
|
73
|
+
def plain_text(value)
|
|
74
|
+
String.new(value.to_s).strip
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def report_once(error)
|
|
78
|
+
first = @reported_lock.synchronize { @reported[error.class.name] ? false : (@reported[error.class.name] = true) }
|
|
79
|
+
return unless first
|
|
80
|
+
|
|
81
|
+
if defined?(::ErrorLog) && ::ErrorLog.respond_to?(:capture!)
|
|
82
|
+
::ErrorLog.capture!(error)
|
|
83
|
+
elsif defined?(::Rails) && ::Rails.respond_to?(:logger) && ::Rails.logger
|
|
84
|
+
::Rails.logger.warn("[studio-navbar] Studio.navbar_user_name failed, showing display_name: " \
|
|
85
|
+
"#{error.class}: #{error.message}")
|
|
86
|
+
end
|
|
87
|
+
rescue StandardError
|
|
88
|
+
nil # reporting is best-effort: the navbar still renders
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
data/lib/studio/version.rb
CHANGED
data/lib/studio.rb
CHANGED
|
@@ -13,6 +13,7 @@ require "studio/js_identifier"
|
|
|
13
13
|
require "studio/partial_block"
|
|
14
14
|
require "studio/sidebar_sections"
|
|
15
15
|
require "studio/navbar_links"
|
|
16
|
+
require "studio/navbar_identity"
|
|
16
17
|
require "studio/profile_sections"
|
|
17
18
|
require "studio/profile_image"
|
|
18
19
|
require "studio/oauth_identity"
|
|
@@ -192,6 +193,33 @@ module Studio
|
|
|
192
193
|
@@navbar_links = links
|
|
193
194
|
end
|
|
194
195
|
|
|
196
|
+
# ---- Navbar identity ----
|
|
197
|
+
# The name the signed-in user nav prints, and the signed-out button's label.
|
|
198
|
+
# Both defaults render the navbar byte-identical to before. The engine has no
|
|
199
|
+
# I18n catalogue, so the label is plain config rather than a locale key.
|
|
200
|
+
# Rules: lib/studio/navbar_identity.rb.
|
|
201
|
+
#
|
|
202
|
+
# config.navbar_user_name = :player_name # a method on the user
|
|
203
|
+
# config.navbar_user_name = ->(user, view) { user.player_name } # or a callable
|
|
204
|
+
# config.sign_in_label = "Sign in"
|
|
205
|
+
#
|
|
206
|
+
# nil means user.display_name. A method or callable that raises, or answers
|
|
207
|
+
# blank, falls back to display_name; a raise is reported once, never a 500.
|
|
208
|
+
mattr_reader :navbar_user_name, default: nil
|
|
209
|
+
|
|
210
|
+
def self.navbar_user_name=(config)
|
|
211
|
+
NavbarIdentity.validate_name!(config)
|
|
212
|
+
NavbarIdentity.reset_reported!
|
|
213
|
+
@@navbar_user_name = config
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
mattr_reader :sign_in_label, default: NavbarIdentity::DEFAULT_SIGN_IN_LABEL
|
|
217
|
+
|
|
218
|
+
def self.sign_in_label=(label)
|
|
219
|
+
NavbarIdentity.validate_label!(label)
|
|
220
|
+
@@sign_in_label = label
|
|
221
|
+
end
|
|
222
|
+
|
|
195
223
|
# ---- The shared profile page (/profile) ----
|
|
196
224
|
# The rows that make up /profile. `nil` — the default — means "the engine's
|
|
197
225
|
# standard page", NOT "no rows": a brand-new app with an empty initializer gets
|
|
@@ -902,6 +930,11 @@ module Studio
|
|
|
902
930
|
NavbarLinks.resolve(navbar_links, view)
|
|
903
931
|
end
|
|
904
932
|
|
|
933
|
+
# The signed-in user's navbar name for a view (lib/studio/navbar_identity.rb).
|
|
934
|
+
def self.navbar_user_name_for(user, view = nil)
|
|
935
|
+
NavbarIdentity.user_name(user, view, config: navbar_user_name)
|
|
936
|
+
end
|
|
937
|
+
|
|
905
938
|
# The engine's standard /profile rows. Hosts compose against this rather than
|
|
906
939
|
# restating a literal list, so a later release that adds a standard row
|
|
907
940
|
# delivers it to every app that used the seam as intended.
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: studio-engine
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.80.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alex McRitchie
|
|
@@ -573,6 +573,7 @@ files:
|
|
|
573
573
|
- lib/studio/log_rotation.rb
|
|
574
574
|
- lib/studio/mail_transport.rb
|
|
575
575
|
- lib/studio/name_parts.rb
|
|
576
|
+
- lib/studio/navbar_identity.rb
|
|
576
577
|
- lib/studio/navbar_links.rb
|
|
577
578
|
- lib/studio/newsletter.rb
|
|
578
579
|
- lib/studio/oauth_identity.rb
|