turbo_chat 0.1.3 → 0.1.4

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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -0
  3. data/README.md +107 -179
  4. data/app/assets/config/turbo_chat_manifest.js +1 -0
  5. data/app/assets/javascripts/turbo_chat/application.js +1 -0
  6. data/app/assets/javascripts/turbo_chat/invite_picker.js +767 -0
  7. data/app/assets/javascripts/turbo_chat/lifecycle_events.js +13 -0
  8. data/app/assets/javascripts/turbo_chat/realtime.js +28 -0
  9. data/app/assets/javascripts/turbo_chat/shared.js +19 -3
  10. data/app/assets/stylesheets/turbo_chat/application.css +595 -14
  11. data/app/controllers/turbo_chat/chat_memberships_controller.rb +49 -1
  12. data/app/controllers/turbo_chat/chat_messages_controller.rb +18 -1
  13. data/app/controllers/turbo_chat/chats_controller.rb +38 -1
  14. data/app/helpers/turbo_chat/application_helper/config_support.rb +26 -0
  15. data/app/helpers/turbo_chat/application_helper/mention_support.rb +7 -0
  16. data/app/helpers/turbo_chat/application_helper/participant_support.rb +28 -0
  17. data/app/models/turbo_chat/chat.rb +1 -1
  18. data/app/models/turbo_chat/chat_membership.rb +13 -0
  19. data/app/models/turbo_chat/chat_message/broadcasting.rb +6 -2
  20. data/app/models/turbo_chat/chat_message/signals.rb +1 -1
  21. data/app/models/turbo_chat/chat_message.rb +83 -2
  22. data/app/views/layouts/turbo_chat/application.html.erb +1 -0
  23. data/app/views/turbo_chat/chat_messages/_form.html.erb +51 -2
  24. data/app/views/turbo_chat/chat_messages/_system.html.erb +8 -0
  25. data/app/views/turbo_chat/chats/_invite_form.html.erb +28 -0
  26. data/app/views/turbo_chat/chats/_member_entries.html.erb +120 -0
  27. data/app/views/turbo_chat/chats/index.html.erb +8 -2
  28. data/app/views/turbo_chat/chats/show.html.erb +62 -18
  29. data/config/routes.rb +1 -1
  30. data/lib/generators/turbo_chat/install/templates/turbo_chat.rb +89 -18
  31. data/lib/turbo_chat/configuration/blocked_words_support.rb +41 -0
  32. data/lib/turbo_chat/configuration/defaults.rb +95 -0
  33. data/lib/turbo_chat/configuration/emoji_support.rb +40 -0
  34. data/lib/turbo_chat/configuration/role_support.rb +32 -0
  35. data/lib/turbo_chat/configuration.rb +14 -238
  36. data/lib/turbo_chat/engine.rb +1 -0
  37. data/lib/turbo_chat/moderation/chat_actions.rb +44 -0
  38. data/lib/turbo_chat/moderation/member_actions.rb +76 -0
  39. data/lib/turbo_chat/moderation/support.rb +87 -0
  40. data/lib/turbo_chat/moderation.rb +9 -191
  41. data/lib/turbo_chat/permission/mention_support.rb +20 -0
  42. data/lib/turbo_chat/permission/support.rb +63 -0
  43. data/lib/turbo_chat/permission.rb +54 -179
  44. data/lib/turbo_chat/version.rb +1 -1
  45. data/turbo_chat.gemspec +3 -2
  46. metadata +18 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b3acc495a5c2ed02fb0e34b695abcc886324ddd36a2e74908fff04e3804cbcd
4
- data.tar.gz: 540a69f756787516f4425d5c6f02f9d48d48be1668099d7be3c46852cce0cb59
3
+ metadata.gz: 87e5b68d4722dd1f28ae30a9926da8445184f2c3116b66f55c8f948e15e923f8
4
+ data.tar.gz: 4654ef43284576a86f56c64c24d93bf3f6a15da709a81720af9fe38f331f5d6c
5
5
  SHA512:
6
- metadata.gz: 5bd0b0a533a8dc5ff85d27abc937cb3a56cd1ec0a469b109a34dae24443134eac7740b3dbe65afeab4d394401a68ae632502de9849a81fb6711366c934e5da7b
7
- data.tar.gz: 5e70673d9dc958fde377576d1332fc9f869b60d92401a0930045b5e3e70d4f23f1f78afc7a0a7ca98ba5389794b9e2ac50c0453356fc7088f4cabf88f872e1c5
6
+ metadata.gz: 1cfa9becf70f4e79fb3d5907dacefd722e87849b8f0cb3cbc75a6b7baa82680bdee3dc82b9d600bf4ddaee56ceaddbb278611a70b462bf8ff1604947f536e951
7
+ data.tar.gz: b5e6106acb66c2e01447ef4a2b104d28da4206a712bf42a3076f5b981e3165d388e787b2307d70793d89ff550cfefdf73dceaea9e100b7ee50489d3284c8eda0
data/CHANGELOG.md ADDED
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ All notable changes to `turbo_chat` will be documented in this file.
4
+
5
+ ## [0.1.4] - 2026-02-22
6
+
7
+ ### Added
8
+ - Searchable invite picker for chat invitations.
9
+ - Collapsible members panel with fixed-height member list shell.
10
+ - Role management and Turbo member list synchronization in chat UI.
11
+ - Lifecycle system messages for join/leave/moderation events.
12
+ - Config toggles for chat member visibility and composer controls.
13
+
14
+ ### Changed
15
+ - Expanded initializer template guidance and README setup/docs.
16
+ - Refactored core chat modules for simpler internal structure.
17
+ - Improved invite row UX and chat composer behavior.
18
+
19
+ ### Fixed
20
+ - Turbo Stream helper errors in member entry rendering.
21
+ - CI setup to prepare the test database before running `test:all`.
22
+
23
+ ## [0.1.3] - 2026-02-19
24
+ - Initial RubyGems release.
data/README.md CHANGED
@@ -1,65 +1,45 @@
1
1
  # TurboChat
2
2
 
3
- TurboChat is a mountable Rails chat engine for server-rendered apps.
3
+ TurboChat is a mountable Rails chat engine for server-rendered applications.
4
4
 
5
- What you get:
6
- - Turbo Stream chat UI.
7
- - Role-based permissions.
8
- - Mentions, typing signals, invites, moderation.
9
- - Practical customization hooks without rewriting everything.
5
+ Status: actively maintained.
10
6
 
11
- What this is not:
12
- - A hosted chat service.
13
- - A React-first component library.
14
- - A "just add JS" widget.
7
+ ## What You Get
15
8
 
16
- ## Use This If
17
-
18
- Use TurboChat if your app is Rails-first and you want chat now, not after building a custom permission/event/moderation system.
9
+ - Turbo Stream chat UI.
10
+ - Role-based permissions.
11
+ - Mentions, invitations, moderation, and typing signals.
12
+ - A Rails-first path to ship chat quickly.
19
13
 
20
- Skip it if you want fully custom frontend architecture from day one.
14
+ ## Basic Setup
21
15
 
22
- ## Install
16
+ Add the gem:
23
17
 
24
18
  ```ruby
25
19
  # Gemfile
26
20
  gem "turbo_chat"
27
21
  ```
28
22
 
23
+ Install and migrate:
24
+
29
25
  ```bash
30
26
  bundle install
31
27
  bin/rails generate turbo_chat:install
32
28
  bin/rails db:migrate
33
29
  ```
34
30
 
35
- Mount with an explicit helper prefix:
31
+ Mount the engine:
36
32
 
37
33
  ```ruby
38
34
  # config/routes.rb
39
- mount TurboChat::Engine => "/chat", as: "turbo_chat"
35
+ mount TurboChat::Engine => "/", as: "turbo_chat"
40
36
  ```
41
37
 
42
- Opinionated recommendation: mount under `/chat` (or another scoped path), not root.
43
-
44
- ## Participant Resolution (Most Important Section)
45
-
46
- This is where installs usually fail. TurboChat now resolves the current participant in this order:
38
+ This gives you chat routes like `/` (chat index), `/chats`, and `/chats/:id`.
47
39
 
48
- 1. `current_chat_participant` on your host `ApplicationController` (preferred explicit hook).
49
- 2. `config.current_participant_resolver` (custom resolver lambda).
50
- 3. `current_user` (if your app exposes it).
40
+ ## Host App Requirements
51
41
 
52
- If none are available, TurboChat raises `NotImplementedError` with guidance.
53
-
54
- Return value must be:
55
- - `nil` (unauthenticated), or
56
- - a model that uses `acts_as_chat_participant`.
57
-
58
- If you use Devise and your `User` model is a chat participant, this works out of the box.
59
-
60
- ## Host Contract
61
-
62
- ### Participant model opt-in
42
+ ### 1. Mark your participant model
63
43
 
64
44
  ```ruby
65
45
  class User < ApplicationRecord
@@ -67,7 +47,11 @@ class User < ApplicationRecord
67
47
  end
68
48
  ```
69
49
 
70
- ### Optional explicit hook (recommended for clarity)
50
+ ### 2. Resolve the current participant
51
+
52
+ Define `current_chat_participant` in your host `ApplicationController`.
53
+
54
+ Recommended hook:
71
55
 
72
56
  ```ruby
73
57
  class ApplicationController < ActionController::Base
@@ -77,7 +61,12 @@ class ApplicationController < ActionController::Base
77
61
  end
78
62
  ```
79
63
 
80
- ### Optional custom resolver (for non-`current_user` auth)
64
+ Fallback behavior (only if you do not define `current_chat_participant`):
65
+
66
+ 1. `config.current_participant_resolver`
67
+ 2. `current_user`
68
+
69
+ Optional resolver for non-`current_user` auth:
81
70
 
82
71
  ```ruby
83
72
  TurboChat.configure do |config|
@@ -85,35 +74,24 @@ TurboChat.configure do |config|
85
74
  end
86
75
  ```
87
76
 
88
- ## First Working Usage
77
+ ## First Working Example
78
+
79
+ Create a chat and add an admin membership:
89
80
 
90
81
  ```ruby
91
82
  chat = TurboChat::Chat.create!(title: "Support")
92
83
  TurboChat::ChatMembership.create!(chat: chat, participant: current_user, role: :admin)
93
84
  ```
94
85
 
86
+ Link to it:
87
+
95
88
  ```erb
96
89
  <%= link_to "Open chat", turbo_chat.chat_path(chat) %>
97
90
  ```
98
91
 
99
- ## Routes You Actually Need
100
-
101
- From the mounted engine:
102
- - `GET /chats`
103
- - `GET /chats/:id`
104
- - `POST /chats`
105
- - `PATCH /chats/:id/accept`
106
- - `PATCH /chats/:id/decline`
107
- - `PATCH /chats/:id/leave`
108
- - `PATCH /chats/:id/close`
109
- - `PATCH /chats/:id/reopen`
110
- - `POST /chats/:chat_id/chat_memberships`
111
- - `POST /chats/:chat_id/chat_messages`
112
- - `PATCH /chats/:chat_id/chat_messages/:id`
113
-
114
- ## Recommended Baseline Config
92
+ ## Essential Configuration
115
93
 
116
- Keep this simple initially:
94
+ Start with a minimal initializer and only expand when needed:
117
95
 
118
96
  ```ruby
119
97
  TurboChat.configure do |config|
@@ -122,11 +100,8 @@ TurboChat.configure do |config|
122
100
  config.max_chat_participants = 10
123
101
  config.max_message_length = 1000
124
102
  config.message_history_limit = 200
125
- config.active_chat_window = 5.minutes
126
103
 
127
104
  config.enable_mentions = true
128
- config.mention_filter_exclude_self = true
129
- config.mention_filter_hide_roles = true
130
105
  config.enable_emoji_aliases = true
131
106
 
132
107
  config.blocked_words = []
@@ -136,38 +111,25 @@ TurboChat.configure do |config|
136
111
  config.show_timestamp = true
137
112
  config.show_role = false
138
113
 
139
- config.emit_typing_events = false
140
- config.emit_message_events = false
141
- config.emit_mention_events = false
142
- config.emit_invitation_events = false
143
- config.emit_chat_lifecycle_events = false
144
114
  config.emit_moderation_events = false
145
115
  config.emit_blocked_words_events = false
116
+ config.emit_mention_events = false
146
117
  end
147
118
  ```
148
119
 
149
- Opinionated defaults:
150
- - Leave HTML rendering off unless required.
151
- - Leave event emission off unless consumed.
152
- - Keep permissions adapter default until you have a concrete policy gap.
153
-
154
- ## Roles and Permissions
120
+ ## Roles
155
121
 
156
122
  Built-in roles:
123
+
157
124
  - `member`
158
125
  - `moderator`
159
126
  - `admin`
160
127
 
161
- Behavior summary:
162
- - `member`: view/post and member mentions.
163
- - `moderator`: invite, `@all`, role mentions, mute/timeout/ban, delete lower-rank messages.
164
- - `admin`: moderator powers plus close/reopen chat.
128
+ Role behavior:
165
129
 
166
- Hard rules:
167
- - Moderation is rank-based.
168
- - No self-moderation.
169
- - Closed chat blocks posting.
170
- - Muted/timed-out members cannot post.
130
+ - `member`: can view/post and mention members.
131
+ - `moderator`: can invite, mention `@all`/roles, mute/timeout/ban, and delete lower-rank messages.
132
+ - `admin`: can do moderator actions plus close/reopen chats.
171
133
 
172
134
  Custom role example:
173
135
 
@@ -182,53 +144,11 @@ TurboChat.configure do |config|
182
144
  end
183
145
  ```
184
146
 
185
- ## Invitations
186
-
187
- - Invite creation: `POST /chats/:chat_id/chat_memberships`
188
- - Accept: `PATCH /chats/:id/accept`
189
- - Decline: `PATCH /chats/:id/decline`
190
-
191
- Important constraints:
192
- - Invite type must match inviter participant base class.
193
- - Participant limits apply to active memberships.
194
- - Re-invite reactivates existing memberships.
195
-
196
- ## Messages, Mentions, Signals
197
-
198
- ### Messages
199
- - Realtime append/update/remove via Turbo Streams.
200
- - Inline edit for your own messages (permission-gated).
201
- - History capped by `message_history_limit`.
202
-
203
- ### Mentions
204
- - `@username`, `@all`, `@ROLE`.
205
- - Server-side mention permission validation.
206
- - Autocomplete defaults: excludes self, hides roles.
207
-
208
- ### Signals
209
- Automatic typing loop is built in.
210
-
211
- Manual APIs:
212
-
213
- ```ruby
214
- TurboChat::Signals.start!(chat: chat, participant: current_user, signal_type: :thinking)
215
- TurboChat::Signals.replace!(chat: chat, participant: current_user, signal_type: :planning)
216
- TurboChat::Signals.clear!(chat: chat, participant: current_user)
217
- ```
218
-
219
- ```ruby
220
- TurboChat::Signals.with(chat: chat, participant: current_user, signal_type: :thinking) do
221
- # work
222
- end
223
- ```
224
-
225
147
  ## Moderation API
226
148
 
227
149
  ```ruby
228
150
  TurboChat::Moderation.mute_member!(actor: moderator, membership: membership)
229
- TurboChat::Moderation.unmute_member!(actor: moderator, membership: membership)
230
151
  TurboChat::Moderation.timeout_member!(actor: moderator, membership: membership, until_time: 30.minutes.from_now)
231
- TurboChat::Moderation.clear_timeout!(actor: moderator, membership: membership)
232
152
  TurboChat::Moderation.ban_member!(actor: moderator, membership: membership)
233
153
  TurboChat::Moderation.delete_message!(actor: moderator, message: message)
234
154
  TurboChat::Moderation.close_chat!(actor: admin, chat: chat)
@@ -236,74 +156,89 @@ TurboChat::Moderation.reopen_chat!(actor: admin, chat: chat)
236
156
  ```
237
157
 
238
158
  Raises:
159
+
239
160
  - `TurboChat::Moderation::AuthorizationError`
240
161
  - `TurboChat::Moderation::InvalidActionError`
241
162
 
242
- ## Browser Events
243
-
244
- Enable with config flags.
245
-
246
- Event names:
247
- - `turbo-chat:typing-started`
248
- - `turbo-chat:typing-ended`
249
- - `turbo-chat:message-sent`
250
- - `turbo-chat:mention`
251
- - `turbo-chat:invitation-accepted`
252
- - `turbo-chat:chat-invited`
253
- - `turbo-chat:chat-joined`
254
- - `turbo-chat:chat-declined`
255
- - `turbo-chat:chat-left`
256
- - `turbo-chat:chat-closed`
257
- - `turbo-chat:chat-reopened`
163
+ ## Signals API
258
164
 
259
- ```js
260
- document.addEventListener("turbo-chat:message-sent", function (event) {
261
- console.log(event.detail.chatId);
262
- });
165
+ ```ruby
166
+ TurboChat::Signals.start!(chat: chat, participant: current_user, signal_type: :typing)
167
+ TurboChat::Signals.clear!(chat: chat, participant: current_user)
263
168
  ```
264
169
 
265
- Client namespace: `window.TurboChatUI`
170
+ ## Event Emissions
266
171
 
267
- ## ActiveSupport::Notifications
172
+ All event emissions are opt-in.
268
173
 
269
- Moderation (`emit_moderation_events = true`):
270
- - `turbo_chat.moderation.member_muted`
271
- - `turbo_chat.moderation.member_unmuted`
272
- - `turbo_chat.moderation.member_timed_out`
273
- - `turbo_chat.moderation.member_timeout_cleared`
274
- - `turbo_chat.moderation.member_banned`
275
- - `turbo_chat.moderation.message_deleted`
276
- - `turbo_chat.moderation.chat_closed`
277
- - `turbo_chat.moderation.chat_reopened`
174
+ Enable only what you consume:
278
175
 
279
- Blocked words (`emit_blocked_words_events = true`):
280
- - `turbo_chat.blocked_words.detected`
281
- - `turbo_chat.blocked_words.rejected`
282
- - `turbo_chat.blocked_words.scrambled`
176
+ ```ruby
177
+ TurboChat.configure do |config|
178
+ config.emit_typing_events = true
179
+ config.emit_message_events = true
180
+ config.emit_mention_events = true
181
+ config.emit_invitation_events = true
182
+ config.emit_chat_lifecycle_events = true
183
+ config.emit_moderation_events = true
184
+ config.emit_blocked_words_events = true
185
+ end
186
+ ```
283
187
 
284
- ## UI Customization
188
+ Browser events (`CustomEvent`):
285
189
 
286
- Do this in order:
287
- 1. Theme with config (`own_message_hex_color`, `role_message_hex_colors`, `mention_mark_hex_color`, formatters).
288
- 2. Add class-level customization via `message_css_class_resolver`.
289
- 3. Only then override markup partials.
190
+ - `emit_typing_events`: `turbo-chat:typing-started`, `turbo-chat:typing-ended`
191
+ - `emit_message_events`: `turbo-chat:message-sent`
192
+ - `emit_mention_events`: `turbo-chat:mention`
193
+ - `emit_invitation_events`: `turbo-chat:invitation-accepted`
194
+ - `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`
290
195
 
291
- Primary partial override point:
292
- - `app/views/turbo_chat/chat_messages/_message.html.erb`
196
+ Minimal browser listener:
293
197
 
294
- Keep `id="<%= dom_id(chat_message) %>"` on the message wrapper or Turbo replacements break.
198
+ ```js
199
+ [
200
+ "turbo-chat:typing-started",
201
+ "turbo-chat:typing-ended",
202
+ "turbo-chat:message-sent",
203
+ "turbo-chat:mention",
204
+ "turbo-chat:invitation-accepted",
205
+ "turbo-chat:chat-invited",
206
+ "turbo-chat:chat-joined",
207
+ "turbo-chat:chat-declined",
208
+ "turbo-chat:chat-left",
209
+ "turbo-chat:chat-closed",
210
+ "turbo-chat:chat-reopened"
211
+ ].forEach(function (eventName) {
212
+ document.addEventListener(eventName, function (event) {
213
+ console.log(eventName, event.detail);
214
+ });
215
+ });
216
+ ```
295
217
 
296
- ## Internal Names
218
+ Server-side notifications (`ActiveSupport::Notifications`):
297
219
 
298
- Use `TurboChat` in app code.
220
+ - `emit_moderation_events`:
221
+ `turbo_chat.moderation.member_muted`,
222
+ `turbo_chat.moderation.member_unmuted`,
223
+ `turbo_chat.moderation.member_timed_out`,
224
+ `turbo_chat.moderation.member_timeout_cleared`,
225
+ `turbo_chat.moderation.member_banned`,
226
+ `turbo_chat.moderation.message_deleted`,
227
+ `turbo_chat.moderation.chat_closed`,
228
+ `turbo_chat.moderation.chat_reopened`
229
+ - `emit_blocked_words_events`:
230
+ `turbo_chat.blocked_words.detected`,
231
+ `turbo_chat.blocked_words.rejected`,
232
+ `turbo_chat.blocked_words.scrambled`
299
233
 
300
- These are implementation identifiers used by the engine:
301
- - Engine namespace in source: `TurboChat`
302
- - Database table prefix: `turbo_chat_`
303
- - Browser event prefix: `turbo-chat:*`
304
- - `ActiveSupport::Notifications` prefix: `turbo_chat.*`
234
+ Minimal Rails listener:
305
235
 
306
- Mount with `as: "turbo_chat"` and use `turbo_chat.*` route helpers.
236
+ ```ruby
237
+ ActiveSupport::Notifications.subscribe(/turbo_chat\.(moderation|blocked_words)\./) do |*args|
238
+ event = ActiveSupport::Notifications::Event.new(*args)
239
+ Rails.logger.info("[TurboChat] #{event.name} #{event.payload.inspect}")
240
+ end
241
+ ```
307
242
 
308
243
  ## Upgrade
309
244
 
@@ -317,10 +252,3 @@ bin/rails db:migrate
317
252
  - Ruby `>= 3.1`
318
253
  - Rails `>= 7.0`, `< 8.0`
319
254
  - `turbo-rails` `>= 1.4`, `< 3.0`
320
- - PostgreSQL or SQLite
321
-
322
- ## Development
323
-
324
- ```bash
325
- bundle exec rake test
326
- ```
@@ -3,4 +3,5 @@
3
3
  //= link turbo_chat/shared.js
4
4
  //= link turbo_chat/messages.js
5
5
  //= link turbo_chat/realtime.js
6
+ //= link turbo_chat/invite_picker.js
6
7
  //= link turbo_chat/lifecycle_events.js
@@ -1,4 +1,5 @@
1
1
  //= require turbo_chat/shared
2
2
  //= require turbo_chat/messages
3
3
  //= require turbo_chat/realtime
4
+ //= require turbo_chat/invite_picker
4
5
  //= require turbo_chat/lifecycle_events