turbo_chat 0.2.0 → 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.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -1
  3. data/README.md +178 -190
  4. data/app/assets/config/turbo_chat_manifest.js +3 -0
  5. data/app/assets/javascripts/turbo_chat/application.js +3 -0
  6. data/app/assets/javascripts/turbo_chat/invite_picker.js +19 -392
  7. data/app/assets/javascripts/turbo_chat/member_sync.js +426 -0
  8. data/app/assets/javascripts/turbo_chat/mentions.js +366 -0
  9. data/app/assets/javascripts/turbo_chat/messages.js +18 -370
  10. data/app/assets/javascripts/turbo_chat/realtime.js +3 -10
  11. data/app/assets/javascripts/turbo_chat/scroll_proxy.js +379 -0
  12. data/app/assets/javascripts/turbo_chat/shared.js +7 -383
  13. data/app/assets/stylesheets/turbo_chat/application.css +9 -1646
  14. data/app/assets/stylesheets/turbo_chat/base.css +84 -0
  15. data/app/assets/stylesheets/turbo_chat/components.css +193 -0
  16. data/app/assets/stylesheets/turbo_chat/composer.css +241 -0
  17. data/app/assets/stylesheets/turbo_chat/layout.css +307 -0
  18. data/app/assets/stylesheets/turbo_chat/members.css +264 -0
  19. data/app/assets/stylesheets/turbo_chat/menus.css +172 -0
  20. data/app/assets/stylesheets/turbo_chat/messages.css +430 -0
  21. data/app/controllers/turbo_chat/application_controller.rb +3 -7
  22. data/app/controllers/turbo_chat/chat_memberships_controller.rb +35 -1
  23. data/app/controllers/turbo_chat/chat_messages_controller.rb +4 -8
  24. data/app/controllers/turbo_chat/chats_controller.rb +10 -12
  25. data/app/helpers/turbo_chat/application_helper/config_support.rb +42 -32
  26. data/app/helpers/turbo_chat/application_helper/mention_support.rb +3 -3
  27. data/app/helpers/turbo_chat/application_helper/message_rendering.rb +24 -13
  28. data/app/models/turbo_chat/chat.rb +43 -20
  29. data/app/models/turbo_chat/chat_membership.rb +1 -1
  30. data/app/models/turbo_chat/chat_message/blocked_words_moderation.rb +9 -25
  31. data/app/models/turbo_chat/chat_message/body_length_validation.rb +1 -1
  32. data/app/models/turbo_chat/chat_message/broadcasting.rb +2 -6
  33. data/app/models/turbo_chat/chat_message/formatting.rb +3 -7
  34. data/app/models/turbo_chat/chat_message/mention_validation.rb +1 -1
  35. data/app/models/turbo_chat/chat_message/signals.rb +1 -1
  36. data/app/models/turbo_chat/chat_message.rb +3 -8
  37. data/app/views/turbo_chat/chat_messages/_form.html.erb +9 -9
  38. data/app/views/turbo_chat/chat_messages/_message.html.erb +2 -2
  39. data/app/views/turbo_chat/chat_messages/_signals.html.erb +11 -13
  40. data/app/views/turbo_chat/chat_messages/_system.html.erb +1 -1
  41. data/app/views/turbo_chat/chats/_invite_form.html.erb +1 -1
  42. data/app/views/turbo_chat/chats/_member_entries.html.erb +15 -1
  43. data/app/views/turbo_chat/chats/index.html.erb +1 -1
  44. data/app/views/turbo_chat/chats/new.html.erb +4 -7
  45. data/app/views/turbo_chat/chats/show.html.erb +29 -27
  46. data/config/routes.rb +6 -1
  47. data/db/migrate/20260325000016_add_chat_mode_to_turbo_chat_chats.rb +6 -0
  48. data/lib/generators/turbo_chat/install/templates/turbo_chat.rb +8 -0
  49. data/lib/turbo_chat/configuration/defaults.rb +21 -0
  50. data/lib/turbo_chat/configuration.rb +105 -0
  51. data/lib/turbo_chat/moderation/chat_actions.rb +2 -2
  52. data/lib/turbo_chat/moderation/member_actions.rb +2 -1
  53. data/lib/turbo_chat/moderation/support.rb +5 -9
  54. data/lib/turbo_chat/permission/support.rb +6 -2
  55. data/lib/turbo_chat/permission.rb +1 -5
  56. data/lib/turbo_chat/signals.rb +1 -1
  57. data/lib/turbo_chat/version.rb +1 -1
  58. metadata +13 -2
@@ -2,8 +2,11 @@ module TurboChat
2
2
  module ApplicationHelper
3
3
  module ConfigSupport
4
4
  BOOLEAN_CHAT_SETTINGS = {
5
+ enable_mentions: true,
5
6
  mention_filter_exclude_self: true,
6
7
  mention_filter_hide_roles: true,
8
+ emit_typing_events: false,
9
+ emit_message_events: false,
7
10
  emit_mention_events: false,
8
11
  emit_invitation_events: false,
9
12
  emit_chat_lifecycle_events: false,
@@ -21,23 +24,27 @@ module TurboChat
21
24
  composer_add_files_display: false,
22
25
  composer_add_files_active: false,
23
26
  composer_microphone_display: false,
24
- composer_microphone_active: false
27
+ composer_microphone_active: false,
28
+ signal_text_sheen: true,
29
+ show_timestamp: true,
30
+ show_role: false,
31
+ render_message_html: false
25
32
  }.freeze
26
33
 
27
34
  BOOLEAN_CHAT_SETTINGS.each do |setting, default|
28
- define_method("chat_#{setting}?") do
29
- chat_config_boolean(setting, default: default)
35
+ define_method("chat_#{setting}?") do |chat: nil|
36
+ chat_config_boolean(setting, default: default, chat: chat)
30
37
  end
31
38
  end
32
39
 
33
- def chat_composer_placeholder_text
34
- value = chat_config_value(:composer_placeholder_text, default: "start chatting")
40
+ def chat_composer_placeholder_text(chat: nil)
41
+ value = chat_config_value(:composer_placeholder_text, default: "start chatting", chat: chat)
35
42
  normalized = value.to_s.strip
36
43
  normalized.present? ? normalized : "start chatting"
37
44
  end
38
45
 
39
- def chat_message_source_labels
40
- configured = chat_config_value(:message_source_labels, default: {})
46
+ def chat_message_source_labels(chat: nil)
47
+ configured = chat_config_value(:message_source_labels, default: {}, chat: chat)
41
48
  return {} unless configured.respond_to?(:each_pair)
42
49
 
43
50
  configured.each_with_object({}) do |(source_key, label), normalized|
@@ -51,9 +58,9 @@ module TurboChat
51
58
  end
52
59
  end
53
60
 
54
- def chat_message_source_label(source)
61
+ def chat_message_source_label(source, chat: nil)
55
62
  source_key = TurboChat::ChatMessage.normalize_source_key(source)
56
- labels = chat_message_source_labels
63
+ labels = chat_message_source_labels(chat: chat)
57
64
  label = labels[source_key]
58
65
  return label if label.present?
59
66
 
@@ -66,11 +73,12 @@ module TurboChat
66
73
  source_key = TurboChat::ChatMessage.normalize_source_key(chat_message.source)
67
74
  return nil if source_key == TurboChat::ChatMessage::DEFAULT_SOURCE
68
75
 
69
- chat_message_source_label(source_key)
76
+ message_chat = chat_message.respond_to?(:chat) ? chat_message.chat : nil
77
+ chat_message_source_label(source_key, chat: message_chat)
70
78
  end
71
79
 
72
- def chat_style_key
73
- raw_style = chat_config_value(:chat_style, default: "chat_style_bounded")
80
+ def chat_style_key(chat: nil)
81
+ raw_style = chat_config_value(:chat_style, default: "chat_style_bounded", chat: chat)
74
82
  normalized = raw_style.to_s.strip.downcase
75
83
 
76
84
  case normalized
@@ -81,16 +89,16 @@ module TurboChat
81
89
  end
82
90
  end
83
91
 
84
- def chat_unbounded_style?
85
- chat_style_key == "chat_style_unbounded"
92
+ def chat_unbounded_style?(chat: nil)
93
+ chat_style_key(chat: chat) == "chat_style_unbounded"
86
94
  end
87
95
 
88
- def chat_shell_style_class
89
- chat_unbounded_style? ? "chat-shell--style-unbounded" : "chat-shell--style-bounded"
96
+ def chat_shell_style_class(chat: nil)
97
+ chat_unbounded_style?(chat: chat) ? "chat-shell--style-unbounded" : "chat-shell--style-bounded"
90
98
  end
91
99
 
92
- def chat_message_insert_position
93
- raw_position = chat_config_value(:message_insert_position, default: "append_end")
100
+ def chat_message_insert_position(chat: nil)
101
+ raw_position = chat_config_value(:message_insert_position, default: "append_end", chat: chat)
94
102
  normalized = raw_position.to_s.strip.downcase
95
103
 
96
104
  case normalized
@@ -101,30 +109,32 @@ module TurboChat
101
109
  end
102
110
  end
103
111
 
104
- def chat_message_append_start?
105
- chat_message_insert_position == "append_start"
112
+ def chat_message_append_start?(chat: nil)
113
+ chat_message_insert_position(chat: chat) == "append_start"
106
114
  end
107
115
 
108
- def chat_signal_ttl_seconds
109
- value = chat_config_value(:signal_ttl_seconds, default: 60)
116
+ def chat_signal_ttl_seconds(chat: nil)
117
+ value = chat_config_value(:signal_ttl_seconds, default: 60, chat: chat)
110
118
  ttl = value.to_i
111
119
  ttl.positive? ? ttl : 60
112
120
  end
113
121
 
114
- private
122
+ def chat_mode_key(chat)
123
+ TurboChat::Configuration.extract_chat_mode(chat) || :standard
124
+ end
115
125
 
116
- def chat_config_value(method_name, default: nil)
117
- configuration = TurboChat.configuration
118
- return default unless configuration.respond_to?(method_name)
126
+ def chat_assistant_mode?(chat)
127
+ chat_mode_key(chat) == :assistant
128
+ end
129
+
130
+ private
119
131
 
120
- configuration.public_send(method_name)
121
- rescue NoMethodError, TypeError
122
- default
132
+ def chat_config_value(method_name, default: nil, chat: nil)
133
+ TurboChat::Configuration.config_value(method_name, default: default, chat: chat)
123
134
  end
124
135
 
125
- def chat_config_boolean(method_name, default:)
126
- value = chat_config_value(method_name, default: default)
127
- ActiveModel::Type::Boolean.new.cast(value)
136
+ def chat_config_boolean(method_name, default:, chat: nil)
137
+ TurboChat::Configuration.config_boolean(method_name, default: default, chat: chat)
128
138
  end
129
139
 
130
140
  def normalize_config_source_key(value)
@@ -10,8 +10,8 @@ module TurboChat
10
10
  allow_member_mentions = mention_permission.nil? ? true : mention_permission_allows?(mention_permission, :can_mention_members?)
11
11
  allow_all_mentions = mention_permission.nil? ? true : mention_permission_allows?(mention_permission, :can_mention_all?)
12
12
  allow_role_mentions = mention_permission.nil? ? true : mention_permission_allows?(mention_permission, :can_mention_roles?)
13
- allow_role_mentions &&= !chat_mention_filter_hide_roles?
14
- exclude_self_mentions = chat_mention_filter_exclude_self?
13
+ allow_role_mentions &&= !chat_mention_filter_hide_roles?(chat: chat)
14
+ exclude_self_mentions = chat_mention_filter_exclude_self?(chat: chat)
15
15
  viewer_participant = mention_viewer_participant(permission: mention_permission)
16
16
 
17
17
  options = []
@@ -73,7 +73,7 @@ module TurboChat
73
73
  end
74
74
 
75
75
  def chat_mentions_enabled_for?(chat:, permission: nil)
76
- return false unless TurboChat.configuration.enable_mentions
76
+ return false unless chat_enable_mentions?(chat: chat)
77
77
 
78
78
  mention_permission = permission || mention_permission_for(chat)
79
79
  return true if mention_permission.nil?
@@ -16,9 +16,9 @@ module TurboChat
16
16
  "--chat-bubble-bg: #{hex_color}; --chat-bubble-border: #{hex_color};"
17
17
  end
18
18
 
19
- def chat_mentions_container_inline_style
20
- hex_color = normalize_hex_color(chat_config_value(:mention_mark_hex_color)) ||
21
- normalize_hex_color(chat_config_value(:mention_highlight_hex_color))
19
+ def chat_mentions_container_inline_style(chat: nil)
20
+ hex_color = normalize_hex_color(chat_config_value(:mention_mark_hex_color, chat: chat)) ||
21
+ normalize_hex_color(chat_config_value(:mention_highlight_hex_color, chat: chat))
22
22
  return nil if hex_color.blank?
23
23
 
24
24
  mention_mark_background = hex_color_with_alpha(hex_color, alpha: 0.22)
@@ -26,21 +26,25 @@ module TurboChat
26
26
  end
27
27
 
28
28
  def render_chat_message_body(chat_message)
29
+ chat = chat_message.respond_to?(:chat) ? chat_message.chat : nil
29
30
  body = chat_message.body.to_s
30
31
  # html_safe is safe here: decorate_plain_message_text guarantees the body
31
32
  # is html_escaped before any markup injection (see method comment above).
32
- return content_tag(:p, decorate_plain_message_text(body).html_safe, class: "chat-body") unless TurboChat.configuration.render_message_html
33
+ unless chat_render_message_html?(chat: chat)
34
+ return content_tag(:p, decorate_plain_message_text(body, chat: chat).html_safe, class: "chat-body")
35
+ end
33
36
 
34
37
  sanitized_html = sanitize(
35
38
  body,
36
- tags: Array(TurboChat.configuration.message_html_tags),
37
- attributes: Array(TurboChat.configuration.message_html_attributes)
39
+ tags: Array(chat_config_value(:message_html_tags, default: [], chat: chat)),
40
+ attributes: Array(chat_config_value(:message_html_attributes, default: [], chat: chat))
38
41
  )
39
42
  content_tag(:div, sanitized_html, class: "chat-body")
40
43
  end
41
44
 
42
45
  def chat_message_mention_tokens(chat_message)
43
- return [] unless TurboChat.configuration.enable_mentions && chat_message.present?
46
+ chat = chat_message.respond_to?(:chat) ? chat_message.chat : nil
47
+ return [] unless chat_enable_mentions?(chat: chat) && chat_message.present?
44
48
 
45
49
  chat_message.body.to_s.scan(MENTION_PATTERN).uniq
46
50
  end
@@ -48,7 +52,8 @@ module TurboChat
48
52
  private
49
53
 
50
54
  def resolve_custom_message_css_classes(chat_message:, own_message:)
51
- resolver = TurboChat.configuration.message_css_class_resolver
55
+ message_chat = chat_message.respond_to?(:chat) ? chat_message.chat : nil
56
+ resolver = chat_config_value(:message_css_class_resolver, default: nil, chat: message_chat)
52
57
  return [] unless resolver.respond_to?(:call)
53
58
 
54
59
  classes = call_message_css_class_resolver(resolver, chat_message: chat_message, own_message: own_message)
@@ -58,15 +63,21 @@ module TurboChat
58
63
  end
59
64
 
60
65
  def resolve_message_hex_color(chat_message:, own_message:)
66
+ message_chat = chat_message.respond_to?(:chat) ? chat_message.chat : nil
61
67
  role_color = resolve_role_message_hex_color(chat_message: chat_message, own_message: own_message)
62
68
  return role_color if role_color.present?
63
69
 
64
- base_color = own_message ? TurboChat.configuration.own_message_hex_color : TurboChat.configuration.other_message_hex_color
70
+ base_color = if own_message
71
+ chat_config_value(:own_message_hex_color, default: nil, chat: message_chat)
72
+ else
73
+ chat_config_value(:other_message_hex_color, default: nil, chat: message_chat)
74
+ end
65
75
  normalize_hex_color(base_color)
66
76
  end
67
77
 
68
78
  def resolve_role_message_hex_color(chat_message:, own_message:)
69
- role_colors = TurboChat.configuration.role_message_hex_colors
79
+ message_chat = chat_message.respond_to?(:chat) ? chat_message.chat : nil
80
+ role_colors = chat_config_value(:role_message_hex_colors, default: {}, chat: message_chat)
70
81
  return nil unless role_colors.is_a?(Hash)
71
82
 
72
83
  role_key = chat_message_role_key(chat_message)
@@ -112,10 +123,10 @@ module TurboChat
112
123
  # The result is safe to mark as html_safe because no raw user content
113
124
  # reaches the output unescaped. Any change to this pipeline MUST
114
125
  # preserve html_escape as the first step.
115
- def decorate_plain_message_text(body)
126
+ def decorate_plain_message_text(body, chat: nil)
116
127
  formatted = ERB::Util.html_escape(body.to_s)
117
- formatted = apply_emoji_aliases(formatted) if TurboChat.configuration.enable_emoji_aliases
118
- formatted = apply_mention_highlights(formatted) if TurboChat.configuration.enable_mentions
128
+ formatted = apply_emoji_aliases(formatted) if chat_config_boolean(:enable_emoji_aliases, default: true, chat: chat)
129
+ formatted = apply_mention_highlights(formatted) if chat_enable_mentions?(chat: chat)
119
130
  formatted.gsub(/\r\n?|\n/, "<br>")
120
131
  end
121
132
 
@@ -1,5 +1,7 @@
1
1
  module TurboChat
2
2
  class Chat < ApplicationRecord
3
+ enum :chat_mode, { standard: 0, assistant: 1 }, prefix: true, default: :standard
4
+
3
5
  has_many :chat_memberships,
4
6
  class_name: "TurboChat::ChatMembership",
5
7
  dependent: :destroy,
@@ -34,29 +36,39 @@ module TurboChat
34
36
  }
35
37
 
36
38
  def active_signals(window: nil)
37
- cutoff = Time.current - self.class.signal_window_seconds(window)
39
+ cutoff = Time.current - self.class.signal_window_seconds(window, chat: self)
40
+
38
41
  latest_message_at = chat_messages
39
42
  .message
40
43
  .where("created_at >= ?", cutoff)
41
44
  .group(:participant_type, :participant_id)
42
45
  .maximum(:created_at)
43
46
 
44
- recent = chat_messages.signal.where("created_at >= ?", cutoff).ordered.reverse
45
- seen = {}
46
- recent.each_with_object([]) do |message, output|
47
- participant_key = [message.participant_type, message.participant_id]
48
- last_message_time = latest_message_at[participant_key]
49
- next if last_message_time && last_message_time >= message.created_at
50
- next if seen[participant_key]
51
-
52
- output << message
53
- seen[participant_key] = true
54
- end.reverse
47
+ latest_signal_ids = chat_messages.signal
48
+ .where("created_at >= ?", cutoff)
49
+ .group(:participant_type, :participant_id)
50
+ .maximum(:id)
51
+ .values
52
+
53
+ return [] if latest_signal_ids.empty?
54
+
55
+ signals = chat_messages.signal.where(id: latest_signal_ids).ordered
56
+
57
+ signals.reject do |signal|
58
+ participant_key = [signal.participant_type, signal.participant_id]
59
+ last_msg_time = latest_message_at[participant_key]
60
+ last_msg_time && last_msg_time >= signal.created_at
61
+ end
55
62
  end
56
63
 
57
- def visible_messages(limit: TurboChat.configuration.message_history_limit)
64
+ def visible_messages(limit: :configured)
58
65
  relation = chat_messages.timeline
59
- normalized_limit = normalize_message_limit(limit)
66
+ configured_limit = if limit == :configured
67
+ TurboChat::Configuration.config_value(:message_history_limit, default: 200, chat: self)
68
+ else
69
+ limit
70
+ end
71
+ normalized_limit = normalize_message_limit(configured_limit)
60
72
  if normalized_limit
61
73
  recent_ids = relation.reorder(created_at: :desc, id: :desc).limit(normalized_limit).select(:id)
62
74
  relation = relation.where(id: recent_ids)
@@ -69,6 +81,14 @@ module TurboChat
69
81
  @membership_lookup ||= chat_memberships.active.index_by { |m| [m.participant_type, m.participant_id] }
70
82
  end
71
83
 
84
+ def find_active_membership(participant)
85
+ if instance_variable_defined?(:@membership_lookup)
86
+ membership_lookup[[participant.class.base_class.name, participant.id]]
87
+ else
88
+ chat_memberships.active.find_by(participant: participant)
89
+ end
90
+ end
91
+
72
92
  def last_message_at
73
93
  chat_messages.message.maximum(:created_at)
74
94
  end
@@ -77,7 +97,7 @@ module TurboChat
77
97
  message_time = last_message_at
78
98
  return false if message_time.nil?
79
99
 
80
- message_time >= at - self.class.activity_window_seconds(window)
100
+ message_time >= at - self.class.activity_window_seconds(window, chat: self)
81
101
  end
82
102
 
83
103
  def inactive?(window: nil, at: Time.current)
@@ -100,18 +120,21 @@ module TurboChat
100
120
  update!(closed_at: nil)
101
121
  end
102
122
 
103
- def self.activity_window_seconds(window = nil)
104
- value = window.nil? ? TurboChat.configuration.active_chat_window : window
123
+ def self.activity_window_seconds(window = nil, chat: nil)
124
+ value = if window.nil?
125
+ TurboChat::Configuration.config_value(:active_chat_window, default: 5.minutes, chat: chat)
126
+ else
127
+ window
128
+ end
105
129
  seconds = value.to_i
106
130
  return seconds if seconds.positive?
107
131
 
108
132
  raise ArgumentError, "active chat window must be a positive duration"
109
133
  end
110
134
 
111
- def self.signal_window_seconds(window = nil)
135
+ def self.signal_window_seconds(window = nil, chat: nil)
112
136
  value = if window.nil?
113
- configuration = TurboChat.configuration
114
- configuration.respond_to?(:signal_ttl_seconds) ? configuration.signal_ttl_seconds : 60
137
+ TurboChat::Configuration.config_value(:signal_ttl_seconds, default: 60, chat: chat)
115
138
  else
116
139
  window
117
140
  end
@@ -108,7 +108,7 @@ module TurboChat
108
108
  def enforce_chat_participant_limit
109
109
  return if chat.nil?
110
110
 
111
- limit = TurboChat.configuration.max_chat_participants
111
+ limit = TurboChat::Configuration.config_value(:max_chat_participants, default: nil, chat: chat)
112
112
  return if limit.nil?
113
113
 
114
114
  limit = limit.to_i
@@ -37,15 +37,14 @@ module TurboChat
37
37
 
38
38
  def scramble_word(word)
39
39
  source = word.to_s
40
- characters = source.chars
41
- return source if characters.length < 2
40
+ return source if source.length < 2
42
41
 
43
- scrambled = characters.shuffle
44
- if scrambled == characters && characters.uniq.length > 1
45
- scrambled = characters.rotate(1)
46
- end
42
+ chars = scramble_chars_from_configuration
43
+ source.chars.map { chars.sample }.join
44
+ end
47
45
 
48
- scrambled.join
46
+ def scramble_chars_from_configuration
47
+ Array(TurboChat.configuration.effective_blocked_words_scramble_chars).presence || TurboChat::Configuration::DEFAULT_BLOCKED_WORDS_SCRAMBLE_CHARS
49
48
  end
50
49
 
51
50
  def blocked_word_pattern(word)
@@ -53,21 +52,11 @@ module TurboChat
53
52
  end
54
53
 
55
54
  def blocked_words_from_configuration
56
- configuration = TurboChat.configuration
57
- return [] unless configuration.respond_to?(:effective_blocked_words)
58
-
59
- Array(configuration.effective_blocked_words)
60
- rescue NoMethodError, TypeError
61
- []
55
+ Array(TurboChat.configuration.effective_blocked_words)
62
56
  end
63
57
 
64
58
  def blocked_words_action_from_configuration
65
- configuration = TurboChat.configuration
66
- return "reject" unless configuration.respond_to?(:effective_blocked_words_action)
67
-
68
- configuration.effective_blocked_words_action.to_s
69
- rescue NoMethodError, TypeError
70
- "reject"
59
+ TurboChat.configuration.effective_blocked_words_action.to_s
71
60
  end
72
61
 
73
62
  def emit_blocked_words_event(name, blocked_words:, action:, original_body: nil, moderated_body: nil)
@@ -88,12 +77,7 @@ module TurboChat
88
77
  end
89
78
 
90
79
  def blocked_words_events_enabled?
91
- configuration = TurboChat.configuration
92
- return false unless configuration.respond_to?(:emit_blocked_words_events)
93
-
94
- ActiveModel::Type::Boolean.new.cast(configuration.emit_blocked_words_events)
95
- rescue NoMethodError, TypeError
96
- false
80
+ TurboChat::Configuration.config_boolean(:emit_blocked_words_events, default: false, chat: chat)
97
81
  end
98
82
 
99
83
  def scramble_blocked_words_with_event!(blocked_words, matches:, action:)
@@ -6,7 +6,7 @@ module TurboChat
6
6
  private
7
7
 
8
8
  def body_within_max_length
9
- configured_limit = TurboChat.configuration.max_message_length
9
+ configured_limit = TurboChat::Configuration.config_value(:max_message_length, default: 1000, chat: chat)
10
10
  return if configured_limit.nil?
11
11
 
12
12
  limit = configured_limit.to_i
@@ -76,12 +76,8 @@ module TurboChat
76
76
  end
77
77
 
78
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 NoMethodError, TypeError
84
- false
79
+ value = TurboChat::Configuration.config_value(:message_insert_position, default: "append_end", chat: chat).to_s.strip.downcase
80
+ %w[append_start start prepend].include?(value)
85
81
  end
86
82
  end
87
83
  end
@@ -34,7 +34,7 @@ module TurboChat
34
34
 
35
35
  role = membership.effective_role_key
36
36
 
37
- formatter = TurboChat.configuration.role_formatter
37
+ formatter = TurboChat::Configuration.config_value(:role_formatter, default: nil, chat: chat)
38
38
  formatted = apply_formatter(formatter, role, self)
39
39
  return formatted if formatted.present?
40
40
 
@@ -46,15 +46,11 @@ module TurboChat
46
46
  def participant_membership
47
47
  return @participant_membership if instance_variable_defined?(:@participant_membership)
48
48
 
49
- @participant_membership = if chat.respond_to?(:membership_lookup) && chat.instance_variable_defined?(:@membership_lookup)
50
- chat.membership_lookup[[participant_type, participant_id]]
51
- else
52
- chat.chat_memberships.active.find_by(participant: participant)
53
- end
49
+ @participant_membership = chat.find_active_membership(participant)
54
50
  end
55
51
 
56
52
  def formatted_time_for(timestamp)
57
- formatter = TurboChat.configuration.timestamp_formatter
53
+ formatter = TurboChat::Configuration.config_value(:timestamp_formatter, default: nil, chat: chat)
58
54
  formatted = apply_formatter(formatter, timestamp, self)
59
55
  return formatted if formatted.present?
60
56
 
@@ -6,7 +6,7 @@ module TurboChat
6
6
  private
7
7
 
8
8
  def mentions_allowed_for_participant
9
- return unless TurboChat.configuration.enable_mentions
9
+ return unless TurboChat::Configuration.config_boolean(:enable_mentions, default: true, chat: chat)
10
10
 
11
11
  mentions = mention_tokens
12
12
  return if mentions.empty?
@@ -62,7 +62,7 @@ module TurboChat
62
62
  end
63
63
 
64
64
  def replace_participant_signals_on_submit
65
- return unless TurboChat.configuration.replace_signals_on_message_submit
65
+ return unless TurboChat::Configuration.config_boolean(:replace_signals_on_message_submit, default: false, chat: chat)
66
66
  return if chat_id.blank? || participant_type.blank? || participant_id.blank?
67
67
 
68
68
  self.class.where(
@@ -81,7 +81,7 @@ module TurboChat
81
81
  end
82
82
 
83
83
  def create_membership_system_message!(chat:, actor:, event:, subject: nil)
84
- return nil unless system_messages_enabled?
84
+ return nil unless system_messages_enabled?(chat)
85
85
  return nil if chat.nil? || actor.nil?
86
86
 
87
87
  normalized_event = event.to_s.to_sym
@@ -93,13 +93,8 @@ module TurboChat
93
93
  create!(chat: chat, participant: actor, kind: :system, body: body)
94
94
  end
95
95
 
96
- def system_messages_enabled?
97
- configuration = TurboChat.configuration
98
- return true unless configuration.respond_to?(:system_messages)
99
-
100
- ActiveModel::Type::Boolean.new.cast(configuration.system_messages)
101
- rescue NoMethodError, TypeError
102
- true
96
+ def system_messages_enabled?(chat = nil)
97
+ TurboChat::Configuration.config_boolean(:system_messages, default: true, chat: chat)
103
98
  end
104
99
 
105
100
  private
@@ -7,23 +7,23 @@
7
7
  <% current_participant = local_assigns[:current_participant] || current_chat_participant %>
8
8
  <% current_participant_type = current_participant.class.base_class.name %>
9
9
  <% current_participant_id = current_participant.id %>
10
- <% show_add_files_button = chat_composer_add_files_display? %>
11
- <% add_files_button_active = chat_composer_add_files_active? %>
12
- <% show_microphone_button = chat_composer_microphone_display? %>
13
- <% microphone_button_active = chat_composer_microphone_active? %>
10
+ <% show_add_files_button = chat_composer_add_files_display?(chat: chat) %>
11
+ <% add_files_button_active = chat_composer_add_files_active?(chat: chat) %>
12
+ <% show_microphone_button = chat_composer_microphone_display?(chat: chat) %>
13
+ <% microphone_button_active = chat_composer_microphone_active?(chat: chat) %>
14
14
  <% composer_shell_classes = ["chat-composer-shell"] %>
15
15
  <% composer_shell_classes << "chat-composer-shell--no-leading-tool" unless show_add_files_button %>
16
16
  <div class="chat-composer"
17
17
  data-chat-composer
18
18
  data-chat-id="<%= chat.id %>"
19
- data-chat-emit-typing-events="<%= TurboChat.configuration.emit_typing_events %>"
20
- data-chat-emit-message-events="<%= TurboChat.configuration.emit_message_events %>"
19
+ data-chat-emit-typing-events="<%= chat_emit_typing_events?(chat: chat) %>"
20
+ data-chat-emit-message-events="<%= chat_emit_message_events?(chat: chat) %>"
21
21
  data-chat-enable-mentions="<%= mentions_enabled %>"
22
22
  data-chat-can-mention-members="<%= can_mention_members %>"
23
23
  data-chat-can-mention-all="<%= can_mention_all %>"
24
24
  data-chat-can-mention-roles="<%= can_mention_roles %>"
25
- data-chat-mention-filter-exclude-self="<%= chat_mention_filter_exclude_self? %>"
26
- data-chat-mention-filter-hide-roles="<%= chat_mention_filter_hide_roles? %>"
25
+ data-chat-mention-filter-exclude-self="<%= chat_mention_filter_exclude_self?(chat: chat) %>"
26
+ data-chat-mention-filter-hide-roles="<%= chat_mention_filter_hide_roles?(chat: chat) %>"
27
27
  data-chat-self-participant-type="<%= current_participant_type %>"
28
28
  data-chat-self-participant-id="<%= current_participant_id %>"
29
29
  data-chat-mention-options="<%= json_escape(mention_options.to_json) %>">
@@ -41,7 +41,7 @@
41
41
  rows: 1,
42
42
  required: true,
43
43
  class: "chat-composer-input",
44
- placeholder: chat_composer_placeholder_text,
44
+ placeholder: chat_composer_placeholder_text(chat: chat),
45
45
  data: { chat_message_input: true } %>
46
46
  <% if show_microphone_button %>
47
47
  <button type="button"
@@ -1,6 +1,6 @@
1
1
  <% own_message = own_chat_message?(chat_message) %>
2
- <% show_timestamp = TurboChat.configuration.show_timestamp %>
3
- <% show_role = TurboChat.configuration.show_role %>
2
+ <% show_timestamp = chat_show_timestamp?(chat: chat_message.chat) %>
3
+ <% show_role = chat_show_role?(chat: chat_message.chat) %>
4
4
  <% role_label = show_role ? chat_message.formatted_participant_role : nil %>
5
5
  <% source_badge_label = chat_message_source_badge_label(chat_message) %>
6
6
  <% edited_message = chat_message.respond_to?(:edited?) && chat_message.edited? %>
@@ -1,11 +1,13 @@
1
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) %>
2
+ chat_show_self_signals?(chat: chat)
3
+ else
4
+ TurboChat::Configuration.config_boolean(:show_self_signals, default: false, chat: chat)
5
+ end %>
6
+ <% signal_text_sheen_enabled = if respond_to?(:chat_signal_text_sheen?, true)
7
+ chat_signal_text_sheen?(chat: chat)
8
+ else
9
+ TurboChat::Configuration.config_boolean(:signal_text_sheen, default: true, chat: chat)
10
+ end %>
9
11
  <% current_participant = respond_to?(:current_chat_participant, true) ? current_chat_participant : nil %>
10
12
  <% current_participant_type = current_participant&.class&.base_class&.name %>
11
13
  <% current_participant_id = current_participant&.id %>
@@ -29,12 +31,8 @@
29
31
  signal_message.signal_type.to_s
30
32
  end %>
31
33
  <strong><%= signal_message.participant_display_name %></strong>
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 %>
34
+ <span class="chat-signal-text<%= " chat-signal-text--pulse" if signal_text_sheen_enabled %>">
35
+ <%= signal_text %>
38
36
  </span>
39
37
  </div>
40
38
  <% end %>
@@ -1,6 +1,6 @@
1
1
  <article id="<%= dom_id(chat_message) %>" class="chat-system-message">
2
2
  <p class="chat-system-message__body"><%= chat_message.body %></p>
3
- <% if TurboChat.configuration.show_timestamp %>
3
+ <% if chat_show_timestamp?(chat: chat_message.chat) %>
4
4
  <time class="chat-system-message__timestamp" datetime="<%= chat_message.created_at.iso8601 %>">
5
5
  <%= chat_message.formatted_timestamp %>
6
6
  </time>
@@ -24,5 +24,5 @@
24
24
  Type at least 2 characters.
25
25
  </p>
26
26
  </div>
27
- <%= f.submit "Invite", class: "chat-btn chat-btn--small" %>
27
+ <%= f.submit "Invite", class: "chat-btn chat-btn--small", data: { chat_invite_submit: true } %>
28
28
  <% end %>