turbo_chat 0.1.12 → 0.1.14
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 +20 -0
- data/README.md +54 -34
- data/app/assets/javascripts/turbo_chat/shared.js +11 -0
- data/app/controllers/turbo_chat/application_controller.rb +10 -1
- data/app/controllers/turbo_chat/chat_messages_controller.rb +1 -1
- data/app/controllers/turbo_chat/chats_controller.rb +1 -1
- data/app/helpers/turbo_chat/application_helper/config_support.rb +24 -0
- data/app/models/turbo_chat/chat.rb +6 -1
- data/app/models/turbo_chat/chat_message/broadcasting.rb +30 -7
- data/app/views/turbo_chat/chat_messages/_signals.html.erb +20 -15
- data/app/views/turbo_chat/chats/show.html.erb +9 -4
- data/lib/generators/turbo_chat/install/templates/turbo_chat.rb +68 -66
- data/lib/turbo_chat/configuration/defaults.rb +66 -17
- data/lib/turbo_chat/configuration/emoji_support.rb +4 -4
- data/lib/turbo_chat/configuration.rb +69 -4
- data/lib/turbo_chat/permission.rb +11 -0
- data/lib/turbo_chat/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b1c6f0dd5991186ff33af55ffef201d7ab37af996f24884e4e25fb2fe8bf6b0c
|
|
4
|
+
data.tar.gz: e0d4b6d10fff71303104ea2d0f797d62222d694d4a27aa7201699730abad6c7a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4968592c0c8462d24a43db894c60229fbcb6d172d0036612934d22e4713e66e454712d2b28c6ce53b9b5e34f8560e6b5f1e7cd5f624e6cfbe696fa1ddfd3747f
|
|
7
|
+
data.tar.gz: eafbf1fdfbc738c0922ca8885c53537eac0377c25ce494dfc04a70038a25e3e63fad432960c39b6b31e25bfa2ce5782cc052e44a797a771d14c53d3a8e7cf0f3
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `turbo_chat` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.1.14] - 2026-02-28
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Scoped configuration namespaces for clearer organization: `config.chat`, `config.chat_message`, `config.style`, `config.moderation`, `config.events`, and `config.signals`.
|
|
9
|
+
- Scoped installer initializer output with inline guidance grouped by scope.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Updated README examples to use scoped configuration.
|
|
13
|
+
- Flat configuration aliases (for example `config.max_message_length`) remain supported for backward compatibility.
|
|
14
|
+
|
|
15
|
+
## [0.1.13] - 2026-02-26
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
- Configurable timeline insert behavior via `config.message_insert_position` (`append_end` or `append_start`).
|
|
19
|
+
- Configurable composer disable toggle via `config.disable_input` to hide input and reject message posts.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
- Signal indicators now render status text for standard signal types (`typing`, `thinking`, `planning`) so shimmer styling can pass over the status text only (not participant names).
|
|
23
|
+
- `config.signal_text_sheen` is now consistently cast as a boolean during signal rendering.
|
|
24
|
+
|
|
5
25
|
## [0.1.12] - 2026-02-26
|
|
6
26
|
|
|
7
27
|
### Added
|
data/README.md
CHANGED
|
@@ -77,7 +77,7 @@ end
|
|
|
77
77
|
Resolution order:
|
|
78
78
|
|
|
79
79
|
1. Host `ApplicationController#current_chat_participant` (if defined)
|
|
80
|
-
2. `config.current_participant_resolver` (if configured)
|
|
80
|
+
2. `config.chat.current_participant_resolver` (if configured)
|
|
81
81
|
3. `current_user` (if available)
|
|
82
82
|
4. Raise `NotImplementedError`
|
|
83
83
|
|
|
@@ -85,7 +85,7 @@ Optional resolver for non-`current_user` auth:
|
|
|
85
85
|
|
|
86
86
|
```ruby
|
|
87
87
|
TurboChat.configure do |config|
|
|
88
|
-
config.current_participant_resolver = ->(controller) { controller.send(:current_member) }
|
|
88
|
+
config.chat.current_participant_resolver = ->(controller) { controller.send(:current_member) }
|
|
89
89
|
end
|
|
90
90
|
```
|
|
91
91
|
|
|
@@ -110,32 +110,36 @@ Start with a minimal initializer and only expand when needed:
|
|
|
110
110
|
|
|
111
111
|
```ruby
|
|
112
112
|
TurboChat.configure do |config|
|
|
113
|
-
config.permission_adapter = TurboChat::Permission
|
|
114
|
-
|
|
115
|
-
config.max_chat_participants = 10
|
|
116
|
-
config.max_message_length = 1000
|
|
117
|
-
config.message_history_limit = 200
|
|
118
|
-
|
|
119
|
-
config.enable_mentions = true
|
|
120
|
-
config.enable_emoji_aliases = true
|
|
121
|
-
|
|
122
|
-
config.blocked_words = []
|
|
123
|
-
config.blocked_words_action = :reject # or :scramble
|
|
124
|
-
|
|
125
|
-
config.render_message_html = false
|
|
126
|
-
config.show_timestamp = true
|
|
127
|
-
config.show_role = false
|
|
128
|
-
config.message_source_labels = TurboChat::Configuration::DEFAULT_MESSAGE_SOURCE_LABELS.dup
|
|
129
|
-
config.chat_style = "chat_style_bounded"
|
|
130
|
-
config.
|
|
131
|
-
config.
|
|
132
|
-
|
|
133
|
-
config.
|
|
134
|
-
|
|
135
|
-
config.
|
|
113
|
+
config.chat.permission_adapter = TurboChat::Permission
|
|
114
|
+
|
|
115
|
+
config.chat.max_chat_participants = 10
|
|
116
|
+
config.chat_message.max_message_length = 1000
|
|
117
|
+
config.chat_message.message_history_limit = 200
|
|
118
|
+
|
|
119
|
+
config.chat_message.enable_mentions = true
|
|
120
|
+
config.chat_message.enable_emoji_aliases = true
|
|
121
|
+
|
|
122
|
+
config.chat_message.blocked_words = []
|
|
123
|
+
config.chat_message.blocked_words_action = :reject # or :scramble
|
|
124
|
+
|
|
125
|
+
config.chat_message.render_message_html = false
|
|
126
|
+
config.style.show_timestamp = true
|
|
127
|
+
config.style.show_role = false
|
|
128
|
+
config.chat_message.message_source_labels = TurboChat::Configuration::DEFAULT_MESSAGE_SOURCE_LABELS.dup
|
|
129
|
+
config.style.chat_style = "chat_style_bounded"
|
|
130
|
+
config.chat_message.message_insert_position = "append_end" # or "append_start"
|
|
131
|
+
config.chat.disable_input = false
|
|
132
|
+
config.signals.signal_ttl_seconds = 60
|
|
133
|
+
config.style.signal_text_sheen = true
|
|
134
|
+
|
|
135
|
+
config.moderation.emit_moderation_events = false
|
|
136
|
+
config.moderation.emit_blocked_words_events = false
|
|
137
|
+
config.events.emit_mention_events = false
|
|
136
138
|
end
|
|
137
139
|
```
|
|
138
140
|
|
|
141
|
+
Flat aliases (for example `config.max_message_length`) still work for backward compatibility.
|
|
142
|
+
|
|
139
143
|
## Message Ingest API
|
|
140
144
|
|
|
141
145
|
Post messages as a specific participant, including external sources like WhatsApp:
|
|
@@ -168,7 +172,7 @@ Source labels shown in message badges are configurable:
|
|
|
168
172
|
|
|
169
173
|
```ruby
|
|
170
174
|
TurboChat.configure do |config|
|
|
171
|
-
config.message_source_labels = {
|
|
175
|
+
config.chat_message.message_source_labels = {
|
|
172
176
|
"app" => "In App",
|
|
173
177
|
"whatsapp" => "WhatsApp",
|
|
174
178
|
"sms_gateway" => "SMS"
|
|
@@ -180,7 +184,23 @@ Chat UI layout style is configurable:
|
|
|
180
184
|
|
|
181
185
|
```ruby
|
|
182
186
|
TurboChat.configure do |config|
|
|
183
|
-
config.chat_style = "chat_style_unbounded" # or "chat_style_bounded"
|
|
187
|
+
config.style.chat_style = "chat_style_unbounded" # or "chat_style_bounded"
|
|
188
|
+
end
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
Timeline insert position is configurable:
|
|
192
|
+
|
|
193
|
+
```ruby
|
|
194
|
+
TurboChat.configure do |config|
|
|
195
|
+
config.chat_message.message_insert_position = "append_end" # or "append_start"
|
|
196
|
+
end
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Composer input can be disabled globally:
|
|
200
|
+
|
|
201
|
+
```ruby
|
|
202
|
+
TurboChat.configure do |config|
|
|
203
|
+
config.chat.disable_input = true
|
|
184
204
|
end
|
|
185
205
|
```
|
|
186
206
|
|
|
@@ -244,13 +264,13 @@ Enable only what you consume:
|
|
|
244
264
|
|
|
245
265
|
```ruby
|
|
246
266
|
TurboChat.configure do |config|
|
|
247
|
-
config.emit_typing_events = true
|
|
248
|
-
config.emit_message_events = true
|
|
249
|
-
config.emit_mention_events = true
|
|
250
|
-
config.emit_invitation_events = true
|
|
251
|
-
config.emit_chat_lifecycle_events = true
|
|
252
|
-
config.emit_moderation_events = true
|
|
253
|
-
config.emit_blocked_words_events = true
|
|
267
|
+
config.events.emit_typing_events = true
|
|
268
|
+
config.events.emit_message_events = true
|
|
269
|
+
config.events.emit_mention_events = true
|
|
270
|
+
config.events.emit_invitation_events = true
|
|
271
|
+
config.events.emit_chat_lifecycle_events = true
|
|
272
|
+
config.moderation.emit_moderation_events = true
|
|
273
|
+
config.moderation.emit_blocked_words_events = true
|
|
254
274
|
end
|
|
255
275
|
```
|
|
256
276
|
|
|
@@ -419,6 +419,17 @@
|
|
|
419
419
|
return;
|
|
420
420
|
}
|
|
421
421
|
|
|
422
|
+
var insertPosition = String(container.dataset.chatMessageInsertPosition || "").trim().toLowerCase();
|
|
423
|
+
if (insertPosition === "append_start") {
|
|
424
|
+
var firstMessage = container.firstElementChild;
|
|
425
|
+
if (!firstMessage) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
container.scrollTop = Math.max(0, firstMessage.offsetTop - 2);
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
422
433
|
var lastMessage = container.lastElementChild;
|
|
423
434
|
if (!lastMessage) {
|
|
424
435
|
return;
|
|
@@ -27,7 +27,7 @@ module TurboChat
|
|
|
27
27
|
return [send(:current_user), "#current_user"]
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
raise NotImplementedError, "Define #current_chat_participant, configure TurboChat.configuration.current_participant_resolver, or expose #current_user"
|
|
30
|
+
raise NotImplementedError, "Define #current_chat_participant, configure TurboChat.configuration.chat.current_participant_resolver, or expose #current_user"
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
def invoke_current_participant_resolver(resolver)
|
|
@@ -58,9 +58,18 @@ module TurboChat
|
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
def authorize_post_message!(chat)
|
|
61
|
+
return head(:forbidden) if chat_input_disabled?
|
|
61
62
|
return if permission_for(chat).can_post_message?
|
|
62
63
|
|
|
63
64
|
head :forbidden
|
|
64
65
|
end
|
|
66
|
+
|
|
67
|
+
def chat_input_disabled?
|
|
68
|
+
configuration = TurboChat.configuration
|
|
69
|
+
value = configuration.respond_to?(:disable_input) ? configuration.disable_input : false
|
|
70
|
+
ActiveModel::Type::Boolean.new.cast(value)
|
|
71
|
+
rescue StandardError
|
|
72
|
+
false
|
|
73
|
+
end
|
|
65
74
|
end
|
|
66
75
|
end
|
|
@@ -82,7 +82,7 @@ module TurboChat
|
|
|
82
82
|
def respond_to_chat_message_create_failure
|
|
83
83
|
@chat_messages = @chat.visible_messages
|
|
84
84
|
@chat_permission = permission_for(@chat)
|
|
85
|
-
@can_post_message = @chat_permission.can_post_message?
|
|
85
|
+
@can_post_message = @chat_permission.can_post_message? && !chat_input_disabled?
|
|
86
86
|
@show_members = chat_config_boolean(:show_members, default: true)
|
|
87
87
|
respond_to do |format|
|
|
88
88
|
format.turbo_stream { render "turbo_chat/chats/show", status: :unprocessable_entity }
|
|
@@ -61,7 +61,7 @@ module TurboChat
|
|
|
61
61
|
@chat_lifecycle_event = chat_lifecycle_event_payload
|
|
62
62
|
@chat_permission = permission_for(@chat)
|
|
63
63
|
@chat_messages = @chat.visible_messages
|
|
64
|
-
@can_post_message = @chat_permission.can_post_message?
|
|
64
|
+
@can_post_message = @chat_permission.can_post_message? && !chat_input_disabled?
|
|
65
65
|
@show_members = show_members_enabled?
|
|
66
66
|
@can_invite_member = permission_allows?(@chat_permission, :can_invite_member?)
|
|
67
67
|
@can_manage_member_permissions = permission_allows?(@chat_permission, :can_grant_member_permissions?)
|
|
@@ -8,6 +8,8 @@ module TurboChat
|
|
|
8
8
|
emit_invitation_events: false,
|
|
9
9
|
emit_chat_lifecycle_events: false,
|
|
10
10
|
show_members: true,
|
|
11
|
+
show_self_signals: false,
|
|
12
|
+
disable_input: false,
|
|
11
13
|
composer_add_files_display: false,
|
|
12
14
|
composer_add_files_active: false,
|
|
13
15
|
composer_microphone_display: false,
|
|
@@ -79,6 +81,28 @@ module TurboChat
|
|
|
79
81
|
chat_unbounded_style? ? "chat-shell--style-unbounded" : "chat-shell--style-bounded"
|
|
80
82
|
end
|
|
81
83
|
|
|
84
|
+
def chat_message_insert_position
|
|
85
|
+
raw_position = chat_config_value(:message_insert_position, default: "append_end")
|
|
86
|
+
normalized = raw_position.to_s.strip.downcase
|
|
87
|
+
|
|
88
|
+
case normalized
|
|
89
|
+
when "append_start", "start", "prepend"
|
|
90
|
+
"append_start"
|
|
91
|
+
else
|
|
92
|
+
"append_end"
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def chat_message_append_start?
|
|
97
|
+
chat_message_insert_position == "append_start"
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def chat_signal_ttl_seconds
|
|
101
|
+
value = chat_config_value(:signal_ttl_seconds, default: 60)
|
|
102
|
+
ttl = value.to_i
|
|
103
|
+
ttl.positive? ? ttl : 60
|
|
104
|
+
end
|
|
105
|
+
|
|
82
106
|
private
|
|
83
107
|
|
|
84
108
|
def chat_config_value(method_name, default: nil)
|
|
@@ -105,7 +105,12 @@ module TurboChat
|
|
|
105
105
|
end
|
|
106
106
|
|
|
107
107
|
def self.signal_window_seconds(window = nil)
|
|
108
|
-
value = window.nil?
|
|
108
|
+
value = if window.nil?
|
|
109
|
+
configuration = TurboChat.configuration
|
|
110
|
+
configuration.respond_to?(:signal_ttl_seconds) ? configuration.signal_ttl_seconds : 60
|
|
111
|
+
else
|
|
112
|
+
window
|
|
113
|
+
end
|
|
109
114
|
seconds = value.to_i
|
|
110
115
|
return seconds if seconds.positive?
|
|
111
116
|
|
|
@@ -10,13 +10,8 @@ module TurboChat
|
|
|
10
10
|
|
|
11
11
|
stream = stream_name
|
|
12
12
|
|
|
13
|
-
if appendable_timeline_message?
|
|
14
|
-
|
|
15
|
-
stream,
|
|
16
|
-
target: ActionView::RecordIdentifier.dom_id(chat, :messages),
|
|
17
|
-
partial: CHAT_MESSAGE_PARTIAL,
|
|
18
|
-
locals: { chat_message: self }
|
|
19
|
-
)
|
|
13
|
+
if appendable_timeline_message?
|
|
14
|
+
broadcast_timeline_create(stream)
|
|
20
15
|
end
|
|
21
16
|
|
|
22
17
|
broadcast_update_to(
|
|
@@ -60,6 +55,34 @@ module TurboChat
|
|
|
60
55
|
def appendable_timeline_message?
|
|
61
56
|
message? || system?
|
|
62
57
|
end
|
|
58
|
+
|
|
59
|
+
def broadcast_timeline_create(stream)
|
|
60
|
+
options = {
|
|
61
|
+
target: ActionView::RecordIdentifier.dom_id(chat, :messages),
|
|
62
|
+
partial: CHAT_MESSAGE_PARTIAL,
|
|
63
|
+
locals: { chat_message: self }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if append_start_position?
|
|
67
|
+
return unless respond_to?(:broadcast_prepend_to)
|
|
68
|
+
|
|
69
|
+
broadcast_prepend_to(stream, **options)
|
|
70
|
+
return
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
return unless respond_to?(:broadcast_append_to)
|
|
74
|
+
|
|
75
|
+
broadcast_append_to(stream, **options)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def append_start_position?
|
|
79
|
+
configuration = TurboChat.configuration
|
|
80
|
+
value = configuration.respond_to?(:message_insert_position) ? configuration.message_insert_position : "append_end"
|
|
81
|
+
normalized = value.to_s.strip.downcase
|
|
82
|
+
%w[append_start start prepend].include?(normalized)
|
|
83
|
+
rescue StandardError
|
|
84
|
+
false
|
|
85
|
+
end
|
|
63
86
|
end
|
|
64
87
|
end
|
|
65
88
|
end
|
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
<% show_self_signals =
|
|
2
|
-
|
|
1
|
+
<% show_self_signals = if respond_to?(:chat_show_self_signals?, true)
|
|
2
|
+
chat_show_self_signals?
|
|
3
|
+
else
|
|
4
|
+
configuration = TurboChat.configuration
|
|
5
|
+
value = configuration.respond_to?(:show_self_signals) ? configuration.show_self_signals : false
|
|
6
|
+
ActiveModel::Type::Boolean.new.cast(value)
|
|
7
|
+
end %>
|
|
8
|
+
<% signal_text_sheen_enabled = ActiveModel::Type::Boolean.new.cast(TurboChat.configuration.signal_text_sheen) %>
|
|
3
9
|
<% current_participant = respond_to?(:current_chat_participant, true) ? current_chat_participant : nil %>
|
|
4
10
|
<% current_participant_type = current_participant&.class&.base_class&.name %>
|
|
5
11
|
<% current_participant_id = current_participant&.id %>
|
|
@@ -17,19 +23,18 @@
|
|
|
17
23
|
data-chat-signal-at="<%= signal_message.created_at.to_i %>"
|
|
18
24
|
data-chat-signal-participant-type="<%= signal_message.participant_type %>"
|
|
19
25
|
data-chat-signal-participant-id="<%= signal_message.participant_id %>">
|
|
26
|
+
<% signal_text = if signal_message.signal_type_custom? && signal_message.signal_text.present?
|
|
27
|
+
signal_message.signal_text
|
|
28
|
+
else
|
|
29
|
+
signal_message.signal_type.to_s
|
|
30
|
+
end %>
|
|
20
31
|
<strong><%= signal_message.participant_display_name %></strong>
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
</span>
|
|
29
|
-
<% else %>
|
|
30
|
-
<span class="chat-dots">
|
|
31
|
-
<i></i><i></i><i></i>
|
|
32
|
-
</span>
|
|
33
|
-
<% end %>
|
|
32
|
+
<span class="chat-signal-text<%= " chat-signal-text--sheen" if signal_text_sheen_enabled %>">
|
|
33
|
+
<% if signal_text_sheen_enabled %>
|
|
34
|
+
<span class="chat-signal-text-sheen"><%= signal_text %></span>
|
|
35
|
+
<% else %>
|
|
36
|
+
<%= signal_text %>
|
|
37
|
+
<% end %>
|
|
38
|
+
</span>
|
|
34
39
|
</div>
|
|
35
40
|
<% end %>
|
|
@@ -10,6 +10,10 @@
|
|
|
10
10
|
("chat-shell--closed" if @chat.closed?),
|
|
11
11
|
("chat-shell--can-manage-member-permissions" if @can_manage_member_permissions)
|
|
12
12
|
].compact.join(" ") %>
|
|
13
|
+
<% message_insert_position = chat_message_insert_position %>
|
|
14
|
+
<% chat_messages = @chat_messages.to_a %>
|
|
15
|
+
<% chat_messages.reverse! if message_insert_position == "append_start" %>
|
|
16
|
+
<% can_render_composer = @can_post_message && !chat_disable_input? %>
|
|
13
17
|
<section class="<%= chat_shell_classes %>"
|
|
14
18
|
data-chat-id="<%= @chat.id %>"
|
|
15
19
|
data-chat-style="<%= chat_style_key %>"
|
|
@@ -113,6 +117,7 @@
|
|
|
113
117
|
<div id="<%= dom_id(@chat, :messages) %>"
|
|
114
118
|
class="chat-messages"
|
|
115
119
|
data-chat-id="<%= @chat.id %>"
|
|
120
|
+
data-chat-message-insert-position="<%= message_insert_position %>"
|
|
116
121
|
data-chat-self-participant-type="<%= current_participant_type %>"
|
|
117
122
|
data-chat-self-participant-id="<%= current_participant_id %>"
|
|
118
123
|
data-chat-self-mention-tokens="<%= json_escape(self_mention_tokens.to_json) %>"
|
|
@@ -122,20 +127,20 @@
|
|
|
122
127
|
data-chat-emit-mention-events="<%= chat_emit_mention_events? %>"
|
|
123
128
|
data-chat-can-edit-own-messages="<%= @can_edit_own_messages %>"
|
|
124
129
|
<%= %(style="#{mention_container_style}") if mention_container_style.present? %>>
|
|
125
|
-
<%= render
|
|
130
|
+
<%= render chat_messages %>
|
|
126
131
|
</div>
|
|
127
132
|
|
|
128
133
|
<div id="<%= dom_id(@chat, :signals) %>"
|
|
129
134
|
class="chat-signals"
|
|
130
|
-
data-chat-show-self-signals="<%=
|
|
131
|
-
data-chat-signal-ttl-seconds="<%=
|
|
135
|
+
data-chat-show-self-signals="<%= chat_show_self_signals? %>"
|
|
136
|
+
data-chat-signal-ttl-seconds="<%= chat_signal_ttl_seconds %>"
|
|
132
137
|
data-chat-self-participant-type="<%= current_participant_type %>"
|
|
133
138
|
data-chat-self-participant-id="<%= current_participant_id %>">
|
|
134
139
|
<%= render "turbo_chat/chat_messages/signals", chat: @chat %>
|
|
135
140
|
</div>
|
|
136
141
|
</section>
|
|
137
142
|
|
|
138
|
-
<% if
|
|
143
|
+
<% if can_render_composer %>
|
|
139
144
|
<%= render "turbo_chat/chat_messages/form",
|
|
140
145
|
chat: @chat,
|
|
141
146
|
chat_message: @chat_message,
|
|
@@ -1,118 +1,120 @@
|
|
|
1
1
|
TurboChat.configure do |config|
|
|
2
|
-
#
|
|
3
|
-
config.permission_adapter = TurboChat::Permission
|
|
2
|
+
# Chat scope
|
|
3
|
+
config.chat.permission_adapter = TurboChat::Permission
|
|
4
4
|
# Class that answers permission checks for participants/chats.
|
|
5
5
|
|
|
6
6
|
# Authentication
|
|
7
|
-
# config.current_participant_resolver = ->(controller) { controller.send(:current_member) }
|
|
7
|
+
# config.chat.current_participant_resolver = ->(controller) { controller.send(:current_member) }
|
|
8
8
|
# Fallback resolver when you do not define `current_chat_participant`.
|
|
9
9
|
|
|
10
|
-
#
|
|
11
|
-
config.max_chat_participants = 10
|
|
10
|
+
# Chat limits
|
|
11
|
+
config.chat.max_chat_participants = 10
|
|
12
12
|
# Maximum active members allowed in a chat.
|
|
13
|
-
config.max_message_length = 1000
|
|
13
|
+
config.chat_message.max_message_length = 1000
|
|
14
14
|
# Maximum characters allowed in a message body.
|
|
15
|
-
config.message_history_limit = 200
|
|
15
|
+
config.chat_message.message_history_limit = 200
|
|
16
16
|
# Number of recent messages loaded in chat history.
|
|
17
|
-
config.active_chat_window = 5.minutes
|
|
17
|
+
config.chat.active_chat_window = 5.minutes
|
|
18
18
|
# Duration used to classify chats as active.
|
|
19
|
+
config.chat.show_members = true
|
|
20
|
+
# Shows member list panel in chat UI.
|
|
21
|
+
config.chat.system_messages = true
|
|
22
|
+
# Shows system timeline messages (joins, invites, moderation).
|
|
23
|
+
# config.chat.disable_input = true
|
|
24
|
+
# Disables composer rendering and blocks message submissions.
|
|
19
25
|
|
|
20
|
-
#
|
|
21
|
-
config.enable_mentions = true
|
|
26
|
+
# Chat message behavior
|
|
27
|
+
config.chat_message.enable_mentions = true
|
|
22
28
|
# Enables mention parsing/highlighting and mention UI.
|
|
23
|
-
config.mention_filter_exclude_self = true
|
|
29
|
+
config.chat_message.mention_filter_exclude_self = true
|
|
24
30
|
# Hides self from mention suggestions.
|
|
25
|
-
config.mention_filter_hide_roles = true
|
|
31
|
+
config.chat_message.mention_filter_hide_roles = true
|
|
26
32
|
# Hides @ROLE suggestions in mention picker.
|
|
27
|
-
config.enable_emoji_aliases = true
|
|
33
|
+
config.chat_message.enable_emoji_aliases = true
|
|
28
34
|
# Expands :alias: tokens using configured emoji aliases.
|
|
29
|
-
config.emoji_aliases = TurboChat::Configuration::DEFAULT_EMOJI_ALIASES.dup
|
|
35
|
+
config.chat_message.emoji_aliases = TurboChat::Configuration::DEFAULT_EMOJI_ALIASES.dup
|
|
30
36
|
# Hash of emoji alias mappings, e.g. "smile" => "😄".
|
|
31
37
|
|
|
32
38
|
# Blocked words
|
|
33
|
-
config.blocked_words = []
|
|
39
|
+
config.chat_message.blocked_words = []
|
|
34
40
|
# Case-insensitive word list checked before message save.
|
|
35
|
-
config.blocked_words_action = :reject
|
|
41
|
+
config.chat_message.blocked_words_action = :reject
|
|
36
42
|
# `:reject` adds a validation error, `:scramble` rewrites matched words.
|
|
37
|
-
config.blocked_words_scramble_chars = TurboChat::Configuration::DEFAULT_BLOCKED_WORDS_SCRAMBLE_CHARS.dup
|
|
43
|
+
config.chat_message.blocked_words_scramble_chars = TurboChat::Configuration::DEFAULT_BLOCKED_WORDS_SCRAMBLE_CHARS.dup
|
|
38
44
|
# Character pool used when scrambling blocked words.
|
|
45
|
+
config.chat_message.message_source_labels = TurboChat::Configuration::DEFAULT_MESSAGE_SOURCE_LABELS.dup
|
|
46
|
+
# Maps message source keys to labels shown on non-default source badges.
|
|
47
|
+
# config.chat_message.message_insert_position = "append_end"
|
|
48
|
+
# Timeline insert behavior: `append_end` (near composer) or `append_start` (top of list).
|
|
49
|
+
# config.chat_message.render_message_html = true
|
|
50
|
+
# Renders/sanitizes HTML in message bodies.
|
|
51
|
+
config.chat_message.message_html_tags = %w[a b br code em i li ol p pre strong ul]
|
|
52
|
+
# Allowed HTML tags when `render_message_html` is true.
|
|
53
|
+
config.chat_message.message_html_attributes = %w[href target rel class]
|
|
54
|
+
# Allowed HTML attributes when `render_message_html` is true.
|
|
55
|
+
config.chat_message.timestamp_formatter = ->(timestamp, _chat_message) { I18n.l(timestamp.in_time_zone, format: :long) }
|
|
56
|
+
# Formatter lambda for displayed timestamps.
|
|
57
|
+
config.chat_message.role_formatter = ->(role, _chat_message) { role.to_s.humanize }
|
|
58
|
+
# Formatter lambda for displayed role labels.
|
|
39
59
|
|
|
40
|
-
#
|
|
41
|
-
# config.mention_mark_hex_color = "b42318"
|
|
60
|
+
# Style scope
|
|
61
|
+
# config.style.mention_mark_hex_color = "b42318"
|
|
42
62
|
# Mention highlight color (without #).
|
|
43
|
-
# config.mention_highlight_hex_color = "b42318"
|
|
63
|
+
# config.style.mention_highlight_hex_color = "b42318"
|
|
44
64
|
# Legacy mention highlight color fallback.
|
|
45
|
-
# config.own_message_hex_color = "eef6ff"
|
|
65
|
+
# config.style.own_message_hex_color = "eef6ff"
|
|
46
66
|
# Bubble background color for current participant messages.
|
|
47
|
-
# config.other_message_hex_color = "ffffff"
|
|
67
|
+
# config.style.other_message_hex_color = "ffffff"
|
|
48
68
|
# Bubble background color for other participant messages.
|
|
49
|
-
config.role_message_hex_colors = {}
|
|
69
|
+
config.style.role_message_hex_colors = {}
|
|
50
70
|
# Map role key to message bubble color.
|
|
51
|
-
config.show_timestamp = true
|
|
71
|
+
config.style.show_timestamp = true
|
|
52
72
|
# Shows formatted timestamp on each message.
|
|
53
|
-
# config.show_role = true
|
|
73
|
+
# config.style.show_role = true
|
|
54
74
|
# Shows participant role label near messages.
|
|
55
|
-
config.
|
|
56
|
-
# Shows member list panel in chat UI.
|
|
57
|
-
config.system_messages = true
|
|
58
|
-
# Shows system timeline messages (joins, invites, moderation).
|
|
59
|
-
config.message_source_labels = TurboChat::Configuration::DEFAULT_MESSAGE_SOURCE_LABELS.dup
|
|
60
|
-
# Maps message source keys to labels shown on non-default source badges.
|
|
61
|
-
config.chat_style = "chat_style_bounded"
|
|
75
|
+
config.style.chat_style = "chat_style_bounded"
|
|
62
76
|
# Chat layout style: `chat_style_bounded` or `chat_style_unbounded`.
|
|
63
|
-
config.composer_placeholder_text = "start chatting"
|
|
77
|
+
config.style.composer_placeholder_text = "start chatting"
|
|
64
78
|
# Placeholder text for the message composer input.
|
|
79
|
+
config.style.signal_text_sheen = true
|
|
80
|
+
# Adds bracketed sheen styling to custom signal text.
|
|
81
|
+
# config.style.message_css_class_resolver = ->(_chat_message) { "chat-message" }
|
|
82
|
+
# Returns extra CSS class names for a message wrapper.
|
|
65
83
|
|
|
66
84
|
# Optional composer controls (disabled by default)
|
|
67
|
-
# config.composer_add_files_display = true
|
|
85
|
+
# config.style.composer_add_files_display = true
|
|
68
86
|
# Shows the add-files control in the composer.
|
|
69
|
-
# config.composer_add_files_active = true
|
|
87
|
+
# config.style.composer_add_files_active = true
|
|
70
88
|
# Enables add-files control interaction.
|
|
71
|
-
# config.composer_microphone_display = true
|
|
89
|
+
# config.style.composer_microphone_display = true
|
|
72
90
|
# Shows microphone control in composer.
|
|
73
|
-
# config.composer_microphone_active = true
|
|
91
|
+
# config.style.composer_microphone_active = true
|
|
74
92
|
# Enables microphone control interaction.
|
|
75
93
|
|
|
76
|
-
#
|
|
77
|
-
# config.emit_typing_events = true
|
|
94
|
+
# Events scope (disabled by default)
|
|
95
|
+
# config.events.emit_typing_events = true
|
|
78
96
|
# Emits `turbo-chat:typing-started` and `turbo-chat:typing-ended`.
|
|
79
|
-
# config.emit_message_events = true
|
|
97
|
+
# config.events.emit_message_events = true
|
|
80
98
|
# Emits `turbo-chat:message-sent`.
|
|
81
|
-
# config.emit_mention_events = true
|
|
99
|
+
# config.events.emit_mention_events = true
|
|
82
100
|
# Emits `turbo-chat:mention`.
|
|
83
|
-
# config.emit_invitation_events = true
|
|
101
|
+
# config.events.emit_invitation_events = true
|
|
84
102
|
# Emits `turbo-chat:invitation-accepted`.
|
|
85
|
-
# config.emit_chat_lifecycle_events = true
|
|
103
|
+
# config.events.emit_chat_lifecycle_events = true
|
|
86
104
|
# Emits `turbo-chat:chat-*` lifecycle events.
|
|
87
105
|
|
|
88
|
-
#
|
|
89
|
-
# config.emit_moderation_events = true
|
|
106
|
+
# Moderation scope (disabled by default)
|
|
107
|
+
# config.moderation.emit_moderation_events = true
|
|
90
108
|
# Emits `turbo_chat.moderation.*` notifications.
|
|
91
|
-
# config.emit_blocked_words_events = true
|
|
109
|
+
# config.moderation.emit_blocked_words_events = true
|
|
92
110
|
# Emits `turbo_chat.blocked_words.*` notifications.
|
|
93
111
|
|
|
94
|
-
#
|
|
95
|
-
config.signal_ttl_seconds = 60
|
|
112
|
+
# Signals scope
|
|
113
|
+
config.signals.signal_ttl_seconds = 60
|
|
96
114
|
# Maximum age (in seconds) for active typing/signal indicators.
|
|
97
|
-
config.signal_text_sheen = true
|
|
98
|
-
# Adds bracketed sheen styling to custom signal text.
|
|
99
115
|
# Optional toggles (disabled by default)
|
|
100
|
-
# config.show_self_signals = true
|
|
116
|
+
# config.signals.show_self_signals = true
|
|
101
117
|
# Shows your own active typing/signal indicators.
|
|
102
|
-
# config.replace_signals_on_message_submit = true
|
|
118
|
+
# config.signals.replace_signals_on_message_submit = true
|
|
103
119
|
# Replaces existing signals when sending a message.
|
|
104
|
-
|
|
105
|
-
# Optional message rendering hooks
|
|
106
|
-
# config.message_css_class_resolver = ->(_chat_message) { "chat-message" }
|
|
107
|
-
# Returns extra CSS class names for a message wrapper.
|
|
108
|
-
# config.render_message_html = true
|
|
109
|
-
# Renders/sanitizes HTML in message bodies.
|
|
110
|
-
config.message_html_tags = %w[a b br code em i li ol p pre strong ul]
|
|
111
|
-
# Allowed HTML tags when `render_message_html` is true.
|
|
112
|
-
config.message_html_attributes = %w[href target rel class]
|
|
113
|
-
# Allowed HTML attributes when `render_message_html` is true.
|
|
114
|
-
config.timestamp_formatter = ->(timestamp, _chat_message) { I18n.l(timestamp.in_time_zone, format: :long) }
|
|
115
|
-
# Formatter lambda for displayed timestamps.
|
|
116
|
-
config.role_formatter = ->(role, _chat_message) { role.to_s.humanize }
|
|
117
|
-
# Formatter lambda for displayed role labels.
|
|
118
120
|
end
|
|
@@ -1,4 +1,18 @@
|
|
|
1
1
|
class TurboChat::Configuration
|
|
2
|
+
def self.build_attribute_scopes(scoped_defaults)
|
|
3
|
+
scoped_defaults.each_with_object({}) do |(scope_name, defaults), mapping|
|
|
4
|
+
defaults.each_key do |attribute|
|
|
5
|
+
if mapping.key?(attribute)
|
|
6
|
+
existing_scope = mapping.fetch(attribute)
|
|
7
|
+
raise ArgumentError, "Duplicate configuration attribute `#{attribute}` across scopes: #{existing_scope}, #{scope_name}"
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
mapping[attribute] = scope_name
|
|
11
|
+
end
|
|
12
|
+
end.freeze
|
|
13
|
+
end
|
|
14
|
+
private_class_method :build_attribute_scopes
|
|
15
|
+
|
|
2
16
|
DEFAULT_ROLE_DEFINITIONS = {
|
|
3
17
|
"member" => {
|
|
4
18
|
name: "Member",
|
|
@@ -51,10 +65,17 @@ class TurboChat::Configuration
|
|
|
51
65
|
"whatsapp" => "WhatsApp"
|
|
52
66
|
}.freeze
|
|
53
67
|
|
|
54
|
-
|
|
68
|
+
CHAT_DEFAULTS = {
|
|
55
69
|
permission_adapter: -> { TurboChat::Permission },
|
|
56
70
|
current_participant_resolver: nil,
|
|
57
71
|
max_chat_participants: 10,
|
|
72
|
+
active_chat_window: -> { 5.minutes },
|
|
73
|
+
show_members: true,
|
|
74
|
+
system_messages: true,
|
|
75
|
+
disable_input: false
|
|
76
|
+
}.freeze
|
|
77
|
+
|
|
78
|
+
CHAT_MESSAGE_DEFAULTS = {
|
|
58
79
|
max_message_length: 1000,
|
|
59
80
|
message_history_limit: 200,
|
|
60
81
|
enable_mentions: true,
|
|
@@ -65,6 +86,16 @@ class TurboChat::Configuration
|
|
|
65
86
|
blocked_words: -> { [] },
|
|
66
87
|
blocked_words_action: DEFAULT_BLOCKED_WORDS_ACTION,
|
|
67
88
|
blocked_words_scramble_chars: -> { DEFAULT_BLOCKED_WORDS_SCRAMBLE_CHARS.dup },
|
|
89
|
+
message_insert_position: "append_end",
|
|
90
|
+
message_source_labels: -> { DEFAULT_MESSAGE_SOURCE_LABELS.dup },
|
|
91
|
+
render_message_html: false,
|
|
92
|
+
message_html_tags: -> { DEFAULT_MESSAGE_HTML_TAGS.dup },
|
|
93
|
+
message_html_attributes: -> { DEFAULT_MESSAGE_HTML_ATTRIBUTES.dup },
|
|
94
|
+
timestamp_formatter: -> { ->(timestamp, _chat_message = nil) { I18n.l(timestamp.in_time_zone, format: :long) } },
|
|
95
|
+
role_formatter: -> { ->(role, _chat_message = nil) { role.to_s.humanize } }
|
|
96
|
+
}.freeze
|
|
97
|
+
|
|
98
|
+
STYLE_DEFAULTS = {
|
|
68
99
|
mention_mark_hex_color: nil,
|
|
69
100
|
mention_highlight_hex_color: nil,
|
|
70
101
|
own_message_hex_color: nil,
|
|
@@ -72,32 +103,50 @@ class TurboChat::Configuration
|
|
|
72
103
|
role_message_hex_colors: -> { {} },
|
|
73
104
|
show_timestamp: true,
|
|
74
105
|
show_role: false,
|
|
75
|
-
show_members: true,
|
|
76
|
-
system_messages: true,
|
|
77
106
|
composer_placeholder_text: "start chatting",
|
|
78
107
|
composer_add_files_display: false,
|
|
79
108
|
composer_add_files_active: false,
|
|
80
109
|
composer_microphone_display: false,
|
|
81
110
|
composer_microphone_active: false,
|
|
82
|
-
|
|
111
|
+
chat_style: "chat_style_bounded",
|
|
112
|
+
message_css_class_resolver: nil,
|
|
113
|
+
signal_text_sheen: true
|
|
114
|
+
}.freeze
|
|
115
|
+
|
|
116
|
+
MODERATION_DEFAULTS = {
|
|
117
|
+
emit_moderation_events: false,
|
|
118
|
+
emit_blocked_words_events: false
|
|
119
|
+
}.freeze
|
|
120
|
+
|
|
121
|
+
EVENTS_DEFAULTS = {
|
|
83
122
|
emit_typing_events: false,
|
|
84
123
|
emit_message_events: false,
|
|
85
124
|
emit_mention_events: false,
|
|
86
125
|
emit_invitation_events: false,
|
|
87
|
-
emit_chat_lifecycle_events: false
|
|
88
|
-
|
|
89
|
-
|
|
126
|
+
emit_chat_lifecycle_events: false
|
|
127
|
+
}.freeze
|
|
128
|
+
|
|
129
|
+
SIGNALS_DEFAULTS = {
|
|
90
130
|
signal_ttl_seconds: 60,
|
|
91
|
-
signal_text_sheen: true,
|
|
92
131
|
show_self_signals: false,
|
|
93
|
-
replace_signals_on_message_submit: false
|
|
94
|
-
message_css_class_resolver: nil,
|
|
95
|
-
message_source_labels: -> { DEFAULT_MESSAGE_SOURCE_LABELS.dup },
|
|
96
|
-
chat_style: "chat_style_bounded",
|
|
97
|
-
render_message_html: false,
|
|
98
|
-
message_html_tags: -> { DEFAULT_MESSAGE_HTML_TAGS.dup },
|
|
99
|
-
message_html_attributes: -> { DEFAULT_MESSAGE_HTML_ATTRIBUTES.dup },
|
|
100
|
-
timestamp_formatter: -> { ->(timestamp, _chat_message = nil) { I18n.l(timestamp.in_time_zone, format: :long) } },
|
|
101
|
-
role_formatter: -> { ->(role, _chat_message = nil) { role.to_s.humanize } }
|
|
132
|
+
replace_signals_on_message_submit: false
|
|
102
133
|
}.freeze
|
|
134
|
+
|
|
135
|
+
SCOPED_DEFAULTS = {
|
|
136
|
+
chat: CHAT_DEFAULTS,
|
|
137
|
+
chat_message: CHAT_MESSAGE_DEFAULTS,
|
|
138
|
+
style: STYLE_DEFAULTS,
|
|
139
|
+
moderation: MODERATION_DEFAULTS,
|
|
140
|
+
events: EVENTS_DEFAULTS,
|
|
141
|
+
signals: SIGNALS_DEFAULTS
|
|
142
|
+
}.freeze
|
|
143
|
+
|
|
144
|
+
ATTRIBUTE_SCOPES = build_attribute_scopes(SCOPED_DEFAULTS)
|
|
145
|
+
|
|
146
|
+
DEFAULTS = ATTRIBUTE_SCOPES.each_with_object({}) do |(attribute, scope_name), defaults|
|
|
147
|
+
defaults[attribute] = SCOPED_DEFAULTS.fetch(scope_name).fetch(attribute)
|
|
148
|
+
end.freeze
|
|
149
|
+
|
|
150
|
+
SCOPE_NAMES = SCOPED_DEFAULTS.keys.freeze
|
|
151
|
+
ATTRIBUTES = ATTRIBUTE_SCOPES.keys.freeze
|
|
103
152
|
end
|
|
@@ -7,19 +7,19 @@ class TurboChat::Configuration
|
|
|
7
7
|
normalized_value = value.to_s.strip
|
|
8
8
|
raise ArgumentError, "Emoji alias value cannot be blank" if normalized_value.blank?
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
self.emoji_aliases = effective_emoji_aliases.merge(key => normalized_value)
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def remove_emoji_alias(name)
|
|
14
14
|
key = normalize_emoji_alias_key(name)
|
|
15
15
|
return if key.blank?
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
self.emoji_aliases = effective_emoji_aliases.except(key)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
def clear_emoji_aliases! =
|
|
20
|
+
def clear_emoji_aliases! = self.emoji_aliases = {}
|
|
21
21
|
|
|
22
|
-
def reset_emoji_aliases! =
|
|
22
|
+
def reset_emoji_aliases! = self.emoji_aliases = DEFAULT_EMOJI_ALIASES.dup
|
|
23
23
|
|
|
24
24
|
def effective_emoji_aliases
|
|
25
25
|
source = emoji_aliases.is_a?(Hash) ? emoji_aliases : {}
|
|
@@ -8,13 +8,78 @@ class TurboChat::Configuration
|
|
|
8
8
|
include EmojiSupport
|
|
9
9
|
include BlockedWordsSupport
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
class Scope
|
|
12
|
+
class << self
|
|
13
|
+
attr_reader :scope_defaults
|
|
14
|
+
|
|
15
|
+
def configure_defaults(defaults)
|
|
16
|
+
@scope_defaults = defaults
|
|
17
|
+
attr_accessor(*defaults.keys)
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def initialize
|
|
22
|
+
self.class.scope_defaults.each do |attribute, default_value|
|
|
23
|
+
instance_variable_set("@#{attribute}", TurboChat::Configuration.resolve_default_value(default_value))
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class Chat < Scope
|
|
29
|
+
configure_defaults(SCOPED_DEFAULTS.fetch(:chat))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
class ChatMessage < Scope
|
|
33
|
+
configure_defaults(SCOPED_DEFAULTS.fetch(:chat_message))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
class Style < Scope
|
|
37
|
+
configure_defaults(SCOPED_DEFAULTS.fetch(:style))
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class Moderation < Scope
|
|
41
|
+
configure_defaults(SCOPED_DEFAULTS.fetch(:moderation))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class Events < Scope
|
|
45
|
+
configure_defaults(SCOPED_DEFAULTS.fetch(:events))
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class Signals < Scope
|
|
49
|
+
configure_defaults(SCOPED_DEFAULTS.fetch(:signals))
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
SCOPE_CLASSES = {
|
|
53
|
+
chat: Chat,
|
|
54
|
+
chat_message: ChatMessage,
|
|
55
|
+
style: Style,
|
|
56
|
+
moderation: Moderation,
|
|
57
|
+
events: Events,
|
|
58
|
+
signals: Signals
|
|
59
|
+
}.freeze
|
|
60
|
+
|
|
61
|
+
attr_reader(*SCOPE_NAMES)
|
|
62
|
+
|
|
63
|
+
ATTRIBUTE_SCOPES.each do |attribute, scope_name|
|
|
64
|
+
define_method(attribute) do
|
|
65
|
+
public_send(scope_name).public_send(attribute)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
define_method("#{attribute}=") do |value|
|
|
69
|
+
public_send(scope_name).public_send("#{attribute}=", value)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
12
72
|
|
|
13
73
|
def initialize
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
instance_variable_set("@#{attribute}", value)
|
|
74
|
+
SCOPE_CLASSES.each do |scope_name, klass|
|
|
75
|
+
instance_variable_set("@#{scope_name}", klass.new)
|
|
17
76
|
end
|
|
18
77
|
@additional_roles = {}
|
|
19
78
|
end
|
|
79
|
+
|
|
80
|
+
class << self
|
|
81
|
+
def resolve_default_value(default_value)
|
|
82
|
+
default_value.respond_to?(:call) ? default_value.call : (default_value.dup rescue default_value)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
20
85
|
end
|
|
@@ -26,6 +26,7 @@ class TurboChat::Permission
|
|
|
26
26
|
|
|
27
27
|
def can_post_message?
|
|
28
28
|
can_view_chat? &&
|
|
29
|
+
!chat_input_disabled? &&
|
|
29
30
|
role_permission?(:post_message) &&
|
|
30
31
|
!chat_closed? &&
|
|
31
32
|
!actor_membership_muted? &&
|
|
@@ -65,4 +66,14 @@ class TurboChat::Permission
|
|
|
65
66
|
def can_close_chat? = can_view_chat? && role_permission?(:close_chat)
|
|
66
67
|
|
|
67
68
|
def can_reopen_chat? = can_view_chat? && role_permission?(:reopen_chat)
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def chat_input_disabled?
|
|
73
|
+
configuration = TurboChat.configuration
|
|
74
|
+
value = configuration.respond_to?(:disable_input) ? configuration.disable_input : false
|
|
75
|
+
ActiveModel::Type::Boolean.new.cast(value)
|
|
76
|
+
rescue StandardError
|
|
77
|
+
false
|
|
78
|
+
end
|
|
68
79
|
end
|
data/lib/turbo_chat/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: turbo_chat
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.14
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Alexander Haumer
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-02-
|
|
11
|
+
date: 2026-02-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rails
|