turbo_chat 0.1.15 → 0.3.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 +33 -0
- data/README.md +181 -183
- data/app/assets/config/turbo_chat_manifest.js +3 -0
- data/app/assets/javascripts/turbo_chat/application.js +3 -0
- data/app/assets/javascripts/turbo_chat/invite_picker.js +19 -392
- data/app/assets/javascripts/turbo_chat/member_sync.js +426 -0
- data/app/assets/javascripts/turbo_chat/mentions.js +366 -0
- data/app/assets/javascripts/turbo_chat/messages.js +18 -370
- data/app/assets/javascripts/turbo_chat/realtime.js +4 -24
- data/app/assets/javascripts/turbo_chat/scroll_proxy.js +379 -0
- data/app/assets/javascripts/turbo_chat/shared.js +8 -383
- data/app/assets/stylesheets/turbo_chat/application.css +9 -1646
- data/app/assets/stylesheets/turbo_chat/base.css +84 -0
- data/app/assets/stylesheets/turbo_chat/components.css +193 -0
- data/app/assets/stylesheets/turbo_chat/composer.css +241 -0
- data/app/assets/stylesheets/turbo_chat/layout.css +307 -0
- data/app/assets/stylesheets/turbo_chat/members.css +264 -0
- data/app/assets/stylesheets/turbo_chat/menus.css +172 -0
- data/app/assets/stylesheets/turbo_chat/messages.css +430 -0
- data/app/controllers/turbo_chat/application_controller.rb +3 -7
- data/app/controllers/turbo_chat/chat_memberships_controller.rb +36 -2
- data/app/controllers/turbo_chat/chat_messages_controller.rb +4 -8
- data/app/controllers/turbo_chat/chats_controller/event_payload_support.rb +2 -2
- data/app/controllers/turbo_chat/chats_controller/invitation_support.rb +1 -1
- data/app/controllers/turbo_chat/chats_controller.rb +10 -12
- data/app/helpers/turbo_chat/application_helper/config_support.rb +42 -32
- data/app/helpers/turbo_chat/application_helper/mention_support/permission_support.rb +2 -2
- data/app/helpers/turbo_chat/application_helper/mention_support.rb +3 -3
- data/app/helpers/turbo_chat/application_helper/message_rendering.rb +33 -13
- data/app/helpers/turbo_chat/application_helper/participant_support.rb +2 -2
- data/app/models/turbo_chat/chat.rb +47 -20
- data/app/models/turbo_chat/chat_membership.rb +1 -1
- data/app/models/turbo_chat/chat_message/blocked_words_moderation.rb +9 -25
- data/app/models/turbo_chat/chat_message/body_length_validation.rb +1 -1
- data/app/models/turbo_chat/chat_message/broadcasting.rb +2 -6
- data/app/models/turbo_chat/chat_message/formatting.rb +3 -3
- data/app/models/turbo_chat/chat_message/mention_validation.rb +3 -3
- data/app/models/turbo_chat/chat_message/signals.rb +1 -1
- data/app/models/turbo_chat/chat_message.rb +3 -8
- data/app/views/turbo_chat/chat_messages/_form.html.erb +9 -9
- data/app/views/turbo_chat/chat_messages/_message.html.erb +2 -2
- data/app/views/turbo_chat/chat_messages/_signals.html.erb +11 -13
- data/app/views/turbo_chat/chat_messages/_system.html.erb +1 -1
- data/app/views/turbo_chat/chats/_invite_form.html.erb +1 -1
- data/app/views/turbo_chat/chats/_member_entries.html.erb +15 -1
- data/app/views/turbo_chat/chats/index.html.erb +1 -1
- data/app/views/turbo_chat/chats/new.html.erb +4 -7
- data/app/views/turbo_chat/chats/show.html.erb +29 -27
- data/config/routes.rb +6 -1
- data/db/migrate/20260302000015_add_kind_index_to_turbo_chat_chat_messages.rb +6 -0
- data/db/migrate/20260325000016_add_chat_mode_to_turbo_chat_chats.rb +6 -0
- data/lib/generators/turbo_chat/install/templates/turbo_chat.rb +8 -0
- data/lib/turbo_chat/configuration/defaults.rb +21 -0
- data/lib/turbo_chat/configuration.rb +105 -0
- data/lib/turbo_chat/messages.rb +1 -1
- data/lib/turbo_chat/moderation/chat_actions.rb +2 -2
- data/lib/turbo_chat/moderation/member_actions.rb +2 -1
- data/lib/turbo_chat/moderation/support.rb +5 -9
- data/lib/turbo_chat/permission/support.rb +9 -4
- data/lib/turbo_chat/permission.rb +1 -5
- data/lib/turbo_chat/signals.rb +1 -1
- data/lib/turbo_chat/version.rb +1 -1
- metadata +14 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2a123f536b09e56d7dbbe582048c7d5eceff7d043126c947647c567b6004b972
|
|
4
|
+
data.tar.gz: d568733e0d68d5e74c67bf0ab7f0a3ccc6433aa14797575311fe1d9250490835
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1ef3331843b81f06f0f5e77988a03ee6d7039b6afecdf4a744259cf0c704d6e0ebf4cec5e9ad37a61e060fea9c3620f1962dc7e0129e987615d3ddc79b65a81d
|
|
7
|
+
data.tar.gz: 155857795d96fdea994d64ac88fdbf4021900c9066a0343dcd36648c823f7bc30f6798db94a2a3b1bd6b1e11f90e6203cf09641fd719cad19f358b95c978f478
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `turbo_chat` will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.3.0] - 2026-03-25
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- `chat_mode` on chats with `standard` as the default and `assistant` as a built-in alternate mode.
|
|
9
|
+
- Mode-aware configuration overrides via `config.mode(:assistant)` so assistant chats can simplify UI and behavior without affecting standard chats.
|
|
10
|
+
- Built-in assistant-mode defaults for 1:1 assistant conversations, including a two-participant limit and simplified members/invite/mention/moderation defaults.
|
|
11
|
+
- README and installer guidance for chat modes and assistant-mode overrides.
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- Chat UI, rendering helpers, validations, moderation events, and message/signal settings now resolve configuration against the current chat when mode-specific overrides are present.
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- Inviting an already-active participant or a participant with a pending invitation no longer reuses the membership in a way that can silently demote it back to a pending state.
|
|
18
|
+
|
|
19
|
+
## [0.2.0] - 2026-03-03
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- Membership lookup cache (`Chat#membership_lookup`) for efficient per-message role resolution, eliminating N+1 queries when rendering participant roles in message lists.
|
|
23
|
+
- Memoized `actor_membership` in Permission to avoid redundant database queries within a single request.
|
|
24
|
+
- Moderation UI: mute/unmute and remove buttons in the members panel with permission-gated visibility (`can_mute_member?`, `can_ban_member?`).
|
|
25
|
+
- `mute` and `ban` member routes and controller actions backed by `TurboChat::Moderation`.
|
|
26
|
+
- Narrower `chat-shell--narrow` variant for the new-chat page.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
- Narrowed broad `rescue StandardError` clauses to specific exception types (`NoMethodError`, `TypeError`) across controllers, helpers, models, and permission modules for better error visibility during development.
|
|
30
|
+
- Removed duplicated `containerBottomPadding` function from `realtime.js`; now imports the shared version from `shared.js`.
|
|
31
|
+
- New-chat page uses compact inline grid form instead of full-width field and button.
|
|
32
|
+
- Members invite dropdown no longer clipped by panel overflow when expanded.
|
|
33
|
+
- Removed redundant "Conversation active/closed" kicker from chat header.
|
|
34
|
+
- Invite submit button disabled until a participant is selected from the dropdown.
|
|
35
|
+
- Gear button hidden (instead of disabled) for own member entry in the members panel.
|
|
36
|
+
- Manage-member gear toggle now visible for moderators with mute/ban permissions, not only admins with grant permissions.
|
|
37
|
+
|
|
5
38
|
## [0.1.15] - 2026-02-28
|
|
6
39
|
|
|
7
40
|
### Added
|
data/README.md
CHANGED
|
@@ -6,10 +6,12 @@ Status: actively maintained.
|
|
|
6
6
|
|
|
7
7
|
## What You Get
|
|
8
8
|
|
|
9
|
-
- Turbo Stream chat UI.
|
|
10
|
-
- Role-based permissions.
|
|
11
|
-
- Mentions, invitations,
|
|
12
|
-
-
|
|
9
|
+
- Turbo Stream chat UI with two layout styles (bounded card or full-viewport).
|
|
10
|
+
- Role-based permissions with a swappable adapter.
|
|
11
|
+
- Mentions, invitations, inline message editing, and typing signals.
|
|
12
|
+
- Moderation: mute, timeout, ban, and message deletion.
|
|
13
|
+
- System messages for membership lifecycle events.
|
|
14
|
+
- Browser and server-side event hooks.
|
|
13
15
|
|
|
14
16
|
## Basic Setup
|
|
15
17
|
|
|
@@ -59,20 +61,11 @@ class User < ApplicationRecord
|
|
|
59
61
|
end
|
|
60
62
|
```
|
|
61
63
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
You can define `current_chat_participant` in your host `ApplicationController`.
|
|
65
|
-
If you already expose `current_user` and it returns a model using `acts_as_chat_participant`, TurboChat works without adding this method.
|
|
64
|
+
This adds `has_many :chat_memberships` and the associations TurboChat needs to resolve participants in chats.
|
|
66
65
|
|
|
67
|
-
|
|
66
|
+
### 2. Resolve the current participant
|
|
68
67
|
|
|
69
|
-
|
|
70
|
-
class ApplicationController < ActionController::Base
|
|
71
|
-
def current_chat_participant
|
|
72
|
-
current_user
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
```
|
|
68
|
+
If your app exposes `current_user` and it returns a model using `acts_as_chat_participant`, TurboChat works out of the box.
|
|
76
69
|
|
|
77
70
|
Resolution order:
|
|
78
71
|
|
|
@@ -81,7 +74,7 @@ Resolution order:
|
|
|
81
74
|
3. `current_user` (if available)
|
|
82
75
|
4. Raise `NotImplementedError`
|
|
83
76
|
|
|
84
|
-
|
|
77
|
+
For non-`current_user` auth:
|
|
85
78
|
|
|
86
79
|
```ruby
|
|
87
80
|
TurboChat.configure do |config|
|
|
@@ -91,182 +84,177 @@ end
|
|
|
91
84
|
|
|
92
85
|
## First Working Example
|
|
93
86
|
|
|
94
|
-
Create a chat and add an admin membership:
|
|
95
|
-
|
|
96
87
|
```ruby
|
|
97
88
|
chat = TurboChat::Chat.create!(title: "Support")
|
|
98
89
|
TurboChat::ChatMembership.create!(chat: chat, participant: current_user, role: :admin)
|
|
99
90
|
```
|
|
100
91
|
|
|
101
|
-
Link to it:
|
|
102
|
-
|
|
103
92
|
```erb
|
|
104
93
|
<%= link_to "Open chat", turbo_chat.chat_path(chat) %>
|
|
105
94
|
```
|
|
106
95
|
|
|
107
|
-
##
|
|
96
|
+
## Configuration
|
|
97
|
+
|
|
98
|
+
The installer generates a full initializer at `config/initializers/turbo_chat.rb` with every option documented. Start with the defaults and change only what you need.
|
|
108
99
|
|
|
109
|
-
|
|
100
|
+
Config is organized into scoped namespaces:
|
|
110
101
|
|
|
111
102
|
```ruby
|
|
112
103
|
TurboChat.configure do |config|
|
|
113
|
-
|
|
114
|
-
|
|
104
|
+
# Limits
|
|
115
105
|
config.chat.max_chat_participants = 10
|
|
116
106
|
config.chat_message.max_message_length = 1000
|
|
117
|
-
config.chat_message.message_history_limit = 200
|
|
118
107
|
|
|
108
|
+
# Features
|
|
119
109
|
config.chat_message.enable_mentions = true
|
|
120
|
-
config.chat_message.enable_emoji_aliases = true
|
|
121
|
-
|
|
122
110
|
config.chat_message.blocked_words = []
|
|
123
|
-
config.chat_message.blocked_words_action = :reject
|
|
111
|
+
config.chat_message.blocked_words_action = :reject # or :scramble
|
|
124
112
|
|
|
125
|
-
|
|
126
|
-
config.style.
|
|
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"
|
|
113
|
+
# Layout
|
|
114
|
+
config.style.chat_style = "chat_style_bounded" # or "chat_style_unbounded"
|
|
130
115
|
config.chat_message.message_insert_position = "append_end" # or "append_start"
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
config.
|
|
134
|
-
config.
|
|
135
|
-
config.chat.show_invite_fallback_when_members_hidden = true
|
|
136
|
-
config.chat.show_header_title = true
|
|
137
|
-
config.chat.show_header_status = true
|
|
138
|
-
config.chat.show_header_close_action = true
|
|
139
|
-
config.chat.show_header_leave_action = true
|
|
140
|
-
config.chat.show_header_back_action = true
|
|
141
|
-
config.signals.signal_ttl_seconds = 60
|
|
142
|
-
config.style.signal_text_sheen = true
|
|
143
|
-
|
|
144
|
-
config.moderation.emit_moderation_events = false
|
|
145
|
-
config.moderation.emit_blocked_words_events = false
|
|
146
|
-
config.events.emit_mention_events = false
|
|
116
|
+
|
|
117
|
+
# Events (all opt-in, all false by default)
|
|
118
|
+
config.events.emit_typing_events = true
|
|
119
|
+
config.events.emit_message_events = true
|
|
147
120
|
end
|
|
148
121
|
```
|
|
149
122
|
|
|
150
|
-
Flat aliases (
|
|
123
|
+
Available scopes: `config.chat`, `config.chat_message`, `config.style`, `config.events`, `config.moderation`, `config.signals`. Flat aliases (e.g. `config.max_message_length`) still work for backward compatibility.
|
|
151
124
|
|
|
152
|
-
|
|
125
|
+
### Chat Modes
|
|
153
126
|
|
|
154
|
-
|
|
127
|
+
Chats default to `chat_mode: :standard`.
|
|
155
128
|
|
|
156
129
|
```ruby
|
|
157
|
-
TurboChat::
|
|
158
|
-
current_user,
|
|
159
|
-
chat,
|
|
160
|
-
body: "Internal note",
|
|
161
|
-
source: :app
|
|
162
|
-
)
|
|
130
|
+
chat = TurboChat::Chat.create!(title: "Assistant", chat_mode: :assistant)
|
|
163
131
|
```
|
|
164
132
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
```ruby
|
|
168
|
-
TurboChat::Messages.ingest_external!(
|
|
169
|
-
chat: chat,
|
|
170
|
-
participant: current_user,
|
|
171
|
-
body: "Hello from WhatsApp",
|
|
172
|
-
source: :whatsapp,
|
|
173
|
-
external_id: webhook_payload.fetch("message_id"),
|
|
174
|
-
sent_at: webhook_payload["sent_at"]
|
|
175
|
-
)
|
|
176
|
-
```
|
|
133
|
+
Built-in assistant-mode defaults make the chat simpler for 1:1 assistant use cases:
|
|
177
134
|
|
|
178
|
-
`
|
|
135
|
+
- `max_chat_participants = 2`
|
|
136
|
+
- members panel and invite UI hidden
|
|
137
|
+
- mentions disabled
|
|
138
|
+
- system messages disabled
|
|
139
|
+
- chat close action hidden
|
|
140
|
+
- invitation, mention, lifecycle, and moderation events disabled
|
|
179
141
|
|
|
180
|
-
|
|
142
|
+
You can override assistant-mode defaults without changing standard chats:
|
|
181
143
|
|
|
182
144
|
```ruby
|
|
183
145
|
TurboChat.configure do |config|
|
|
184
|
-
config.
|
|
185
|
-
|
|
186
|
-
"whatsapp" => "WhatsApp",
|
|
187
|
-
"sms_gateway" => "SMS"
|
|
188
|
-
}
|
|
146
|
+
config.mode(:assistant).show_members = true
|
|
147
|
+
config.mode(:assistant).enable_mentions = true
|
|
189
148
|
end
|
|
190
149
|
```
|
|
191
150
|
|
|
192
|
-
|
|
151
|
+
### Rate Limiting
|
|
193
152
|
|
|
194
|
-
|
|
195
|
-
TurboChat.configure do |config|
|
|
196
|
-
config.style.chat_style = "chat_style_unbounded" # or "chat_style_bounded"
|
|
197
|
-
end
|
|
198
|
-
```
|
|
153
|
+
TurboChat does not include built-in rate limiting. For production, add request throttling using middleware such as [rack-attack](https://github.com/rack/rack-attack).
|
|
199
154
|
|
|
200
|
-
|
|
155
|
+
## Roles and Permissions
|
|
201
156
|
|
|
202
|
-
|
|
203
|
-
TurboChat.configure do |config|
|
|
204
|
-
config.chat_message.message_insert_position = "append_end" # or "append_start"
|
|
205
|
-
end
|
|
206
|
-
```
|
|
157
|
+
### Built-in roles
|
|
207
158
|
|
|
208
|
-
|
|
159
|
+
| Role | Rank | Key capabilities |
|
|
160
|
+
|------|------|-----------------|
|
|
161
|
+
| `member` | 0 | View, post, mention members, edit own messages |
|
|
162
|
+
| `moderator` | 1 | + invite, `@all`/`@ROLE` mentions, mute/timeout/ban, delete messages |
|
|
163
|
+
| `admin` | 2 | + close/reopen chats, change member roles |
|
|
164
|
+
|
|
165
|
+
Higher-rank roles can act on lower-rank members but not on peers or above.
|
|
166
|
+
|
|
167
|
+
### Custom roles
|
|
209
168
|
|
|
210
169
|
```ruby
|
|
211
170
|
TurboChat.configure do |config|
|
|
212
|
-
config.
|
|
171
|
+
config.add_role(
|
|
172
|
+
:support_agent,
|
|
173
|
+
name: "Support Agent",
|
|
174
|
+
rank: 1,
|
|
175
|
+
permissions: %i[view_chat post_message delete_message]
|
|
176
|
+
)
|
|
213
177
|
end
|
|
214
178
|
```
|
|
215
179
|
|
|
216
|
-
|
|
180
|
+
### Permission adapter
|
|
181
|
+
|
|
182
|
+
All access checks go through `config.chat.permission_adapter` (default: `TurboChat::Permission`). To customize, create a class that implements the same public interface:
|
|
217
183
|
|
|
218
184
|
```ruby
|
|
185
|
+
class CustomPermission < TurboChat::Permission
|
|
186
|
+
def can_post_message?
|
|
187
|
+
super && !participant.suspended?
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
219
191
|
TurboChat.configure do |config|
|
|
220
|
-
config.chat.
|
|
221
|
-
config.chat.show_header_status = false
|
|
222
|
-
config.chat.show_header_close_action = false
|
|
223
|
-
config.chat.show_header_leave_action = false
|
|
224
|
-
config.chat.show_header_back_action = false
|
|
192
|
+
config.chat.permission_adapter = CustomPermission
|
|
225
193
|
end
|
|
226
194
|
```
|
|
227
195
|
|
|
228
|
-
|
|
196
|
+
The adapter must respond to: `can_view_chat?`, `can_create_chat?`, `can_post_message?`, `can_invite_member?`, `can_grant_member_permissions?`, `can_mute_member?`, `can_timeout_member?`, `can_ban_member?`, `can_delete_message?`, `can_edit_message?`, `can_close_chat?`, `can_reopen_chat?`.
|
|
197
|
+
|
|
198
|
+
## Invitations
|
|
199
|
+
|
|
200
|
+
Admins and moderators can invite participants through the members panel. The invited participant sees pending invitations on the chat index with Accept/Decline buttons.
|
|
201
|
+
|
|
202
|
+
Programmatic invitation:
|
|
229
203
|
|
|
230
204
|
```ruby
|
|
231
|
-
TurboChat.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
205
|
+
TurboChat::ChatMembership.create!(
|
|
206
|
+
chat: chat,
|
|
207
|
+
participant: invitee,
|
|
208
|
+
role: :member,
|
|
209
|
+
invitation_accepted: false
|
|
210
|
+
)
|
|
237
211
|
```
|
|
238
212
|
|
|
239
|
-
|
|
213
|
+
Accept/decline routes are built in: `PATCH /chats/:id/accept` and `PATCH /chats/:id/decline`.
|
|
240
214
|
|
|
241
|
-
|
|
215
|
+
## Message Ingest API
|
|
242
216
|
|
|
243
|
-
|
|
244
|
-
- `moderator`
|
|
245
|
-
- `admin`
|
|
217
|
+
Post messages as a specific participant:
|
|
246
218
|
|
|
247
|
-
|
|
219
|
+
```ruby
|
|
220
|
+
TurboChat::Messages.send_message_as(
|
|
221
|
+
current_user,
|
|
222
|
+
chat,
|
|
223
|
+
body: "Internal note",
|
|
224
|
+
source: :app
|
|
225
|
+
)
|
|
226
|
+
```
|
|
248
227
|
|
|
249
|
-
|
|
250
|
-
- `moderator`: can invite, mention `@all`/roles, mute/timeout/ban, and delete lower-rank messages.
|
|
251
|
-
- `admin`: can do moderator actions plus close/reopen chats.
|
|
228
|
+
External ingest with idempotency (`chat_id + source + external_id`):
|
|
252
229
|
|
|
253
|
-
|
|
230
|
+
```ruby
|
|
231
|
+
TurboChat::Messages.ingest_external!(
|
|
232
|
+
chat: chat,
|
|
233
|
+
participant: current_user,
|
|
234
|
+
body: "Hello from WhatsApp",
|
|
235
|
+
source: :whatsapp,
|
|
236
|
+
external_id: webhook_payload.fetch("message_id"),
|
|
237
|
+
sent_at: webhook_payload["sent_at"]
|
|
238
|
+
)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Duplicate webhook deliveries with the same `external_id` resolve to the existing message.
|
|
242
|
+
|
|
243
|
+
Source labels shown in message badges are configurable:
|
|
254
244
|
|
|
255
245
|
```ruby
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
permissions: %i[view_chat post_message delete_message]
|
|
262
|
-
)
|
|
263
|
-
end
|
|
246
|
+
config.chat_message.message_source_labels = {
|
|
247
|
+
"app" => "In App",
|
|
248
|
+
"whatsapp" => "WhatsApp",
|
|
249
|
+
"sms_gateway" => "SMS"
|
|
250
|
+
}
|
|
264
251
|
```
|
|
265
252
|
|
|
266
253
|
## Moderation API
|
|
267
254
|
|
|
268
255
|
```ruby
|
|
269
256
|
TurboChat::Moderation.mute_member!(actor: moderator, membership: membership)
|
|
257
|
+
TurboChat::Moderation.unmute_member!(actor: moderator, membership: membership)
|
|
270
258
|
TurboChat::Moderation.timeout_member!(actor: moderator, membership: membership, until_time: 30.minutes.from_now)
|
|
271
259
|
TurboChat::Moderation.ban_member!(actor: moderator, membership: membership)
|
|
272
260
|
TurboChat::Moderation.delete_message!(actor: moderator, message: message)
|
|
@@ -274,93 +262,97 @@ TurboChat::Moderation.close_chat!(actor: admin, chat: chat)
|
|
|
274
262
|
TurboChat::Moderation.reopen_chat!(actor: admin, chat: chat)
|
|
275
263
|
```
|
|
276
264
|
|
|
277
|
-
|
|
265
|
+
All actions are permission-checked and generate system messages in the chat timeline. Mute, timeout, ban, and role changes are visible to all participants as system messages (disable with `config.chat.system_messages = false`).
|
|
278
266
|
|
|
279
|
-
|
|
280
|
-
- `TurboChat::Moderation::InvalidActionError`
|
|
267
|
+
Raises `TurboChat::Moderation::AuthorizationError` or `TurboChat::Moderation::InvalidActionError`.
|
|
281
268
|
|
|
282
269
|
## Signals API
|
|
283
270
|
|
|
284
271
|
```ruby
|
|
285
|
-
TurboChat::Signals.start!(chat: chat, participant:
|
|
286
|
-
TurboChat::Signals.start!(chat: chat, participant:
|
|
287
|
-
TurboChat::Signals.
|
|
288
|
-
TurboChat::Signals.
|
|
272
|
+
TurboChat::Signals.start!(chat: chat, participant: user, signal_type: :typing)
|
|
273
|
+
TurboChat::Signals.start!(chat: chat, participant: user, signal_type: :thinking)
|
|
274
|
+
TurboChat::Signals.start!(chat: chat, participant: user, signal_type: :planning)
|
|
275
|
+
TurboChat::Signals.custom!(chat: chat, participant: user, signal_text: "Reviewing your request")
|
|
276
|
+
TurboChat::Signals.clear!(chat: chat, participant: user)
|
|
289
277
|
```
|
|
290
278
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
All event emissions are opt-in.
|
|
294
|
-
|
|
295
|
-
Enable only what you consume:
|
|
279
|
+
Block form that auto-clears when the work finishes:
|
|
296
280
|
|
|
297
281
|
```ruby
|
|
298
|
-
TurboChat.
|
|
299
|
-
|
|
300
|
-
config.events.emit_message_events = true
|
|
301
|
-
config.events.emit_mention_events = true
|
|
302
|
-
config.events.emit_invitation_events = true
|
|
303
|
-
config.events.emit_chat_lifecycle_events = true
|
|
304
|
-
config.moderation.emit_moderation_events = true
|
|
305
|
-
config.moderation.emit_blocked_words_events = true
|
|
282
|
+
TurboChat::Signals.with(chat: chat, participant: user, signal_type: :thinking) do
|
|
283
|
+
# long-running operation
|
|
306
284
|
end
|
|
307
285
|
```
|
|
308
286
|
|
|
309
|
-
|
|
287
|
+
Signal lifetime is configurable via `config.signals.signal_ttl_seconds` (default: 60).
|
|
288
|
+
|
|
289
|
+
## Events
|
|
310
290
|
|
|
311
|
-
|
|
312
|
-
- `emit_message_events`: `turbo-chat:message-sent`
|
|
313
|
-
- `emit_mention_events`: `turbo-chat:mention`
|
|
314
|
-
- `emit_invitation_events`: `turbo-chat:invitation-accepted`
|
|
315
|
-
- `emit_chat_lifecycle_events`: `turbo-chat:chat-invited`, `turbo-chat:chat-joined`, `turbo-chat:chat-declined`, `turbo-chat:chat-left`, `turbo-chat:chat-closed`, `turbo-chat:chat-reopened`
|
|
291
|
+
All event emissions are opt-in.
|
|
316
292
|
|
|
317
|
-
|
|
293
|
+
### Browser events (`CustomEvent` on `document`)
|
|
294
|
+
|
|
295
|
+
```ruby
|
|
296
|
+
config.events.emit_typing_events = true # turbo-chat:typing-started, turbo-chat:typing-ended
|
|
297
|
+
config.events.emit_message_events = true # turbo-chat:message-sent
|
|
298
|
+
config.events.emit_mention_events = true # turbo-chat:mention
|
|
299
|
+
config.events.emit_invitation_events = true # turbo-chat:invitation-accepted
|
|
300
|
+
config.events.emit_chat_lifecycle_events = true # turbo-chat:chat-invited, chat-joined, chat-declined,
|
|
301
|
+
# chat-left, chat-closed, chat-reopened
|
|
302
|
+
```
|
|
318
303
|
|
|
319
304
|
```js
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
"turbo-chat:typing-ended",
|
|
323
|
-
"turbo-chat:message-sent",
|
|
324
|
-
"turbo-chat:mention",
|
|
325
|
-
"turbo-chat:invitation-accepted",
|
|
326
|
-
"turbo-chat:chat-invited",
|
|
327
|
-
"turbo-chat:chat-joined",
|
|
328
|
-
"turbo-chat:chat-declined",
|
|
329
|
-
"turbo-chat:chat-left",
|
|
330
|
-
"turbo-chat:chat-closed",
|
|
331
|
-
"turbo-chat:chat-reopened"
|
|
332
|
-
].forEach(function (eventName) {
|
|
333
|
-
document.addEventListener(eventName, function (event) {
|
|
334
|
-
console.log(eventName, event.detail);
|
|
335
|
-
});
|
|
305
|
+
document.addEventListener("turbo-chat:message-sent", function (event) {
|
|
306
|
+
console.log(event.detail); // { chatId: "123" }
|
|
336
307
|
});
|
|
337
308
|
```
|
|
338
309
|
|
|
339
|
-
Server-side
|
|
340
|
-
|
|
341
|
-
- `emit_moderation_events`:
|
|
342
|
-
`turbo_chat.moderation.member_muted`,
|
|
343
|
-
`turbo_chat.moderation.member_unmuted`,
|
|
344
|
-
`turbo_chat.moderation.member_timed_out`,
|
|
345
|
-
`turbo_chat.moderation.member_timeout_cleared`,
|
|
346
|
-
`turbo_chat.moderation.member_banned`,
|
|
347
|
-
`turbo_chat.moderation.message_deleted`,
|
|
348
|
-
`turbo_chat.moderation.chat_closed`,
|
|
349
|
-
`turbo_chat.moderation.chat_reopened`
|
|
350
|
-
- `emit_blocked_words_events`:
|
|
351
|
-
`turbo_chat.blocked_words.detected`,
|
|
352
|
-
`turbo_chat.blocked_words.rejected`,
|
|
353
|
-
`turbo_chat.blocked_words.scrambled`
|
|
310
|
+
### Server-side events (`ActiveSupport::Notifications`)
|
|
354
311
|
|
|
355
|
-
|
|
312
|
+
```ruby
|
|
313
|
+
config.moderation.emit_moderation_events = true # turbo_chat.moderation.member_muted, member_banned, ...
|
|
314
|
+
config.moderation.emit_blocked_words_events = true # turbo_chat.blocked_words.detected, rejected, scrambled
|
|
315
|
+
```
|
|
356
316
|
|
|
357
317
|
```ruby
|
|
358
|
-
ActiveSupport::Notifications.subscribe(/turbo_chat\.
|
|
318
|
+
ActiveSupport::Notifications.subscribe(/turbo_chat\.moderation\./) do |*args|
|
|
359
319
|
event = ActiveSupport::Notifications::Event.new(*args)
|
|
360
320
|
Rails.logger.info("[TurboChat] #{event.name} #{event.payload.inspect}")
|
|
361
321
|
end
|
|
362
322
|
```
|
|
363
323
|
|
|
324
|
+
## Theming
|
|
325
|
+
|
|
326
|
+
TurboChat ships a default theme built on CSS custom properties. Override them in your app stylesheet:
|
|
327
|
+
|
|
328
|
+
```css
|
|
329
|
+
:root {
|
|
330
|
+
--chat-text: #12263f;
|
|
331
|
+
--chat-muted: #5f738a;
|
|
332
|
+
--chat-border: #c9d5e3;
|
|
333
|
+
--chat-surface: #ffffff;
|
|
334
|
+
--chat-surface-soft: #f6f9fd;
|
|
335
|
+
--chat-primary: #1f6edc;
|
|
336
|
+
--chat-primary-dark: #1452ac;
|
|
337
|
+
--chat-primary-soft: #e7f1ff;
|
|
338
|
+
--chat-danger: #b42318;
|
|
339
|
+
--chat-danger-soft: #fff1f3;
|
|
340
|
+
--chat-success: #1f7a40;
|
|
341
|
+
--chat-success-soft: #ecfdf3;
|
|
342
|
+
--chat-mention-highlight-color: #b42318;
|
|
343
|
+
--chat-shadow: 0 20px 48px rgba(16, 36, 58, 0.12);
|
|
344
|
+
}
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Additional style config:
|
|
348
|
+
|
|
349
|
+
```ruby
|
|
350
|
+
config.style.own_message_hex_color = "#e7f1ff"
|
|
351
|
+
config.style.other_message_hex_color = "#ffffff"
|
|
352
|
+
config.style.mention_mark_hex_color = "#b42318"
|
|
353
|
+
config.style.composer_placeholder_text = "Type a message..."
|
|
354
|
+
```
|
|
355
|
+
|
|
364
356
|
## Upgrade
|
|
365
357
|
|
|
366
358
|
```bash
|
|
@@ -368,6 +360,12 @@ bin/rails turbo_chat:install:migrations
|
|
|
368
360
|
bin/rails db:migrate
|
|
369
361
|
```
|
|
370
362
|
|
|
363
|
+
## Known Limitations
|
|
364
|
+
|
|
365
|
+
- **Stream subscriptions after member removal.** Turbo Stream subscriptions persist until page reload. A removed member may see new messages until they navigate away. Stream names are cryptographically signed so this is not a security issue.
|
|
366
|
+
- **Blocked word filtering.** Word-boundary matching can be bypassed with Unicode homoglyphs, zero-width characters, or leetspeak. Treat it as a first line of defense.
|
|
367
|
+
- **Invitable participants cap.** The invite picker returns at most 100 non-member participants. Larger user bases should provide a custom search endpoint.
|
|
368
|
+
|
|
371
369
|
## Dependencies
|
|
372
370
|
|
|
373
371
|
- Ruby `>= 3.1`
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
//= link turbo_chat/application.css
|
|
2
2
|
//= link turbo_chat/application.js
|
|
3
3
|
//= link turbo_chat/shared.js
|
|
4
|
+
//= link turbo_chat/mentions.js
|
|
5
|
+
//= link turbo_chat/scroll_proxy.js
|
|
4
6
|
//= link turbo_chat/messages.js
|
|
5
7
|
//= link turbo_chat/realtime.js
|
|
6
8
|
//= link turbo_chat/invite_picker.js
|
|
9
|
+
//= link turbo_chat/member_sync.js
|
|
7
10
|
//= link turbo_chat/lifecycle_events.js
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
//= require turbo_chat/shared
|
|
2
|
+
//= require turbo_chat/scroll_proxy
|
|
3
|
+
//= require turbo_chat/mentions
|
|
2
4
|
//= require turbo_chat/messages
|
|
3
5
|
//= require turbo_chat/realtime
|
|
4
6
|
//= require turbo_chat/invite_picker
|
|
7
|
+
//= require turbo_chat/member_sync
|
|
5
8
|
//= require turbo_chat/lifecycle_events
|