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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -1
- data/README.md +178 -190
- 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 +3 -10
- data/app/assets/javascripts/turbo_chat/scroll_proxy.js +379 -0
- data/app/assets/javascripts/turbo_chat/shared.js +7 -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 +35 -1
- data/app/controllers/turbo_chat/chat_messages_controller.rb +4 -8
- 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.rb +3 -3
- data/app/helpers/turbo_chat/application_helper/message_rendering.rb +24 -13
- data/app/models/turbo_chat/chat.rb +43 -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 -7
- data/app/models/turbo_chat/chat_message/mention_validation.rb +1 -1
- 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/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/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 +6 -2
- 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 +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
|
-
|
|
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
|
-
|
|
122
|
+
def chat_mode_key(chat)
|
|
123
|
+
TurboChat::Configuration.extract_chat_mode(chat) || :standard
|
|
124
|
+
end
|
|
115
125
|
|
|
116
|
-
def
|
|
117
|
-
|
|
118
|
-
|
|
126
|
+
def chat_assistant_mode?(chat)
|
|
127
|
+
chat_mode_key(chat) == :assistant
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
119
131
|
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
37
|
-
attributes: Array(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
118
|
-
formatted = apply_mention_highlights(formatted) if
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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:
|
|
64
|
+
def visible_messages(limit: :configured)
|
|
58
65
|
relation = chat_messages.timeline
|
|
59
|
-
|
|
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?
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
41
|
-
return source if characters.length < 2
|
|
40
|
+
return source if source.length < 2
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
end
|
|
42
|
+
chars = scramble_chars_from_configuration
|
|
43
|
+
source.chars.map { chars.sample }.join
|
|
44
|
+
end
|
|
47
45
|
|
|
48
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
80
|
-
|
|
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.
|
|
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 =
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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="<%=
|
|
20
|
-
data-chat-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 =
|
|
3
|
-
<% 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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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--
|
|
33
|
-
|
|
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
|
|
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>
|