livechat 0.6.8 → 0.7.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 +8 -0
- data/README.md +21 -0
- data/app/helpers/livechat/widget_helper.rb +7 -0
- data/lib/generators/livechat/install/install_generator.rb +1 -0
- data/lib/generators/livechat/install/templates/initializer.rb +5 -0
- data/lib/livechat/configuration.rb +6 -0
- data/lib/livechat/engine.rb +4 -0
- data/lib/livechat/seeds.rb +75 -0
- data/lib/livechat/version.rb +1 -1
- data/lib/livechat/widget.js +18 -1
- data/lib/livechat/widget.rb +4 -3
- data/lib/livechat.rb +1 -0
- data/lib/tasks/livechat_tasks.rake +9 -0
- metadata +3 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3068cc97c1460ba3cec4f85670bf78b94a98ad38bbc6acf9e244b35b70f7c49c
|
|
4
|
+
data.tar.gz: 0f384fe194ed0cafae68da41e3bcd20d80f7ff56e89aafdb25f10265ba4202c5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 66fcad915c30d22220db8730e8c2f8433e96dd8db1bfae6e33e2e162230a60e93c9829467135526e797c34c4f82e910c48f4c057adb0fa201db97694f673f9ec
|
|
7
|
+
data.tar.gz: f13d5a99530347b8ca35eec44e9e4520e595f1d485353cff4b6700251845e3ff6bbd8c6023a4ffb6a0ba9768f389c7cc6bb07afc862113387a689d153314160b
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.7.0
|
|
4
|
+
|
|
5
|
+
- Added `config.avatar_url`, accepting a URL or per-request callable, to show
|
|
6
|
+
an optional customer-facing avatar in the visitor widget header. Failed
|
|
7
|
+
images disappear cleanly, and external image requests omit the referrer.
|
|
8
|
+
- Added `bin/rails livechat:seed_demo` for idempotent sample conversations in
|
|
9
|
+
development and demo environments.
|
|
10
|
+
|
|
3
11
|
## 0.6.8
|
|
4
12
|
|
|
5
13
|
- Fixed Rails 7.1 compatibility for attachment configuration by avoiding the
|
data/README.md
CHANGED
|
@@ -36,6 +36,16 @@ bin/rails db:migrate
|
|
|
36
36
|
That's it. Visit any page, click the bubble, say hi. Answer yourself at
|
|
37
37
|
`/livechat`.
|
|
38
38
|
|
|
39
|
+
Optional demo data:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
bin/rails livechat:seed_demo
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
It creates two idempotent sample conversations: one open thread with an unread
|
|
46
|
+
visitor reply, and one resolved thread. Running the task again refreshes those
|
|
47
|
+
demo messages instead of duplicating conversations.
|
|
48
|
+
|
|
39
49
|
> [!IMPORTANT]
|
|
40
50
|
> The inbox defaults to **development only**. Set `authorize_agent` before you
|
|
41
51
|
> deploy — see [Configure](#configure).
|
|
@@ -117,6 +127,7 @@ Everything is optional — a fresh install works with zero config. In
|
|
|
117
127
|
| `greeting` | localized default | First message visitors see |
|
|
118
128
|
| `reply_time_text` | localized default | "We usually reply within a few hours" |
|
|
119
129
|
| `launcher_label` | localized default | Text on the bubble |
|
|
130
|
+
| `avatar_url` | `nil` | Customer-facing avatar in the widget header; URL or per-request callable |
|
|
120
131
|
| `accent_color` | `nil` | One hex restyles launcher, header, bubbles, send button |
|
|
121
132
|
| `show_launcher` | `true` | `false` hides the bubble — bring your own entry point |
|
|
122
133
|
| `visitor_label` | name, else email | How a visitor is labelled in the inbox |
|
|
@@ -145,10 +156,20 @@ Livechat.configure do |config|
|
|
|
145
156
|
config.mailer_from = "chat@example.com"
|
|
146
157
|
config.agent_emails = -> { User.where(admin: true).pluck(:email) }
|
|
147
158
|
config.reply_time_text = "We usually reply within an hour."
|
|
159
|
+
config.avatar_url = "/support-avatar.png"
|
|
148
160
|
config.accent_color = "#7c3aed"
|
|
149
161
|
end
|
|
150
162
|
```
|
|
151
163
|
|
|
164
|
+
`avatar_url` can also choose branding from the current request:
|
|
165
|
+
|
|
166
|
+
```ruby
|
|
167
|
+
config.avatar_url = ->(request) { request.env["current_account"]&.support_avatar_url }
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Prefer a same-origin image. If the URL uses another host, allow that host in
|
|
171
|
+
your application's `img-src` Content Security Policy.
|
|
172
|
+
|
|
152
173
|
Gates receive the **raw request**, so they work with any auth:
|
|
153
174
|
|
|
154
175
|
```ruby
|
|
@@ -13,6 +13,7 @@ module Livechat
|
|
|
13
13
|
Widget.snippet(
|
|
14
14
|
locale: I18n.locale,
|
|
15
15
|
authenticated: livechat_visitor.present?,
|
|
16
|
+
avatar_url: livechat_avatar_url,
|
|
16
17
|
nonce: (content_security_policy_nonce if respond_to?(:content_security_policy_nonce))
|
|
17
18
|
).html_safe
|
|
18
19
|
end
|
|
@@ -33,6 +34,12 @@ module Livechat
|
|
|
33
34
|
|
|
34
35
|
private
|
|
35
36
|
|
|
37
|
+
def livechat_avatar_url
|
|
38
|
+
value = Livechat.config.avatar_url
|
|
39
|
+
value = value.call(request) if value.respond_to?(:call)
|
|
40
|
+
value.to_s.presence
|
|
41
|
+
end
|
|
42
|
+
|
|
36
43
|
def livechat_visitor
|
|
37
44
|
return @livechat_visitor if defined?(@livechat_visitor)
|
|
38
45
|
|
|
@@ -29,6 +29,7 @@ module Livechat
|
|
|
29
29
|
say "\nlivechat installed. Run `rails db:migrate`, then add", :green
|
|
30
30
|
say '`<%= livechat_tag %>` before </body> in your layout.'
|
|
31
31
|
say 'Answer visitors at /livechat (development only until you set config.authorize_agent).'
|
|
32
|
+
say 'Optional: run `bin/rails livechat:seed_demo` for sample conversations.'
|
|
32
33
|
say "Set config.mailer_from + config.agent_emails to hear about new messages by email.\n"
|
|
33
34
|
end
|
|
34
35
|
|
|
@@ -45,6 +45,11 @@ Livechat.configure do |config|
|
|
|
45
45
|
# config.reply_time_text = "We usually reply within a few hours."
|
|
46
46
|
# config.launcher_label = "Chat with us"
|
|
47
47
|
|
|
48
|
+
# Optional customer-facing avatar in the widget header. Use a URL string,
|
|
49
|
+
# or resolve one per request (for example, for tenant-specific branding).
|
|
50
|
+
# config.avatar_url = "/support-avatar.png"
|
|
51
|
+
# config.avatar_url = ->(request) { request.env["current_account"]&.support_avatar_url }
|
|
52
|
+
|
|
48
53
|
# Brand color (hex) for the launcher, header, bubbles and send button.
|
|
49
54
|
# Text flips black/white automatically for contrast. nil = built-in blue.
|
|
50
55
|
# config.accent_color = "#7c3aed"
|
|
@@ -44,6 +44,11 @@ module Livechat
|
|
|
44
44
|
# honest line under the greeting — "We usually reply within a few hours."
|
|
45
45
|
attr_accessor :greeting, :reply_time_text, :launcher_label
|
|
46
46
|
|
|
47
|
+
# Optional customer-facing avatar shown in the widget header. Use a URL
|
|
48
|
+
# string, or a callable receiving the request for tenant-specific branding.
|
|
49
|
+
# nil keeps the text-only header.
|
|
50
|
+
attr_accessor :avatar_url
|
|
51
|
+
|
|
47
52
|
# Brand color for the widget (launcher, header, visitor bubbles, send
|
|
48
53
|
# button) as a hex value, e.g. "#7c3aed". The widget picks black or white
|
|
49
54
|
# text automatically for contrast. nil keeps the built-in blue.
|
|
@@ -112,6 +117,7 @@ module Livechat
|
|
|
112
117
|
@greeting = nil
|
|
113
118
|
@reply_time_text = nil
|
|
114
119
|
@launcher_label = nil
|
|
120
|
+
@avatar_url = nil
|
|
115
121
|
@accent_color = nil
|
|
116
122
|
@show_launcher = true
|
|
117
123
|
@agent_emails = nil
|
data/lib/livechat/engine.rb
CHANGED
|
@@ -4,6 +4,10 @@ module Livechat
|
|
|
4
4
|
class Engine < ::Rails::Engine
|
|
5
5
|
isolate_namespace Livechat
|
|
6
6
|
|
|
7
|
+
rake_tasks do
|
|
8
|
+
load File.expand_path('../tasks/livechat_tasks.rake', __dir__)
|
|
9
|
+
end
|
|
10
|
+
|
|
7
11
|
initializer 'livechat.helpers' do
|
|
8
12
|
ActiveSupport.on_load(:action_view) do
|
|
9
13
|
include Livechat::WidgetHelper
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Livechat
|
|
4
|
+
module Seeds
|
|
5
|
+
THREADS = [
|
|
6
|
+
{
|
|
7
|
+
visitor_token: 'livechat-demo-checkout',
|
|
8
|
+
visitor_label: 'Demo Customer',
|
|
9
|
+
visitor_email: 'customer@example.com',
|
|
10
|
+
page_url: '/checkout',
|
|
11
|
+
locale: 'en',
|
|
12
|
+
status: 'open',
|
|
13
|
+
messages: [
|
|
14
|
+
{
|
|
15
|
+
author_type: 'visitor',
|
|
16
|
+
body: 'I am trying to finish checkout, but the payment button keeps spinning.',
|
|
17
|
+
read_at: true
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
author_type: 'agent',
|
|
21
|
+
agent_id: 'livechat-demo-agent',
|
|
22
|
+
agent_label: 'Demo Support',
|
|
23
|
+
body: 'Thanks for the details. I am checking the payment logs now.',
|
|
24
|
+
read_at: true
|
|
25
|
+
},
|
|
26
|
+
{ author_type: 'visitor', body: 'The order number is #DEMO-1042.' }
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
visitor_token: 'livechat-demo-resolved',
|
|
31
|
+
visitor_label: 'Demo Admin',
|
|
32
|
+
visitor_email: 'admin@example.com',
|
|
33
|
+
page_url: '/settings/billing',
|
|
34
|
+
locale: 'en',
|
|
35
|
+
status: 'resolved',
|
|
36
|
+
messages: [
|
|
37
|
+
{ author_type: 'visitor', body: 'Where can I download last month\'s invoice?' },
|
|
38
|
+
{
|
|
39
|
+
author_type: 'agent',
|
|
40
|
+
agent_id: 'livechat-demo-agent',
|
|
41
|
+
agent_label: 'Demo Support',
|
|
42
|
+
body: 'Open Settings, then Billing, then click Download next to the invoice.',
|
|
43
|
+
read_at: true
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
author_type: 'system',
|
|
47
|
+
agent_label: 'Demo Support',
|
|
48
|
+
event: 'resolved'
|
|
49
|
+
}
|
|
50
|
+
]
|
|
51
|
+
}
|
|
52
|
+
].freeze
|
|
53
|
+
|
|
54
|
+
def self.load!
|
|
55
|
+
THREADS.map do |attributes|
|
|
56
|
+
conversation = Livechat::Conversation.find_or_initialize_by(
|
|
57
|
+
visitor_token: attributes.fetch(:visitor_token),
|
|
58
|
+
visitor_id: nil
|
|
59
|
+
)
|
|
60
|
+
conversation.assign_attributes(attributes.except(:messages))
|
|
61
|
+
conversation.save!
|
|
62
|
+
conversation.messages.destroy_all
|
|
63
|
+
attributes.fetch(:messages).each do |message_attributes|
|
|
64
|
+
conversation.messages.create!(message_attributes.merge(read_at_value(message_attributes)))
|
|
65
|
+
end
|
|
66
|
+
conversation.reload
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def self.read_at_value(attributes)
|
|
71
|
+
attributes[:read_at] ? { read_at: Time.current } : {}
|
|
72
|
+
end
|
|
73
|
+
private_class_method :read_at_value
|
|
74
|
+
end
|
|
75
|
+
end
|
data/lib/livechat/version.rb
CHANGED
data/lib/livechat/widget.js
CHANGED
|
@@ -217,14 +217,27 @@
|
|
|
217
217
|
|
|
218
218
|
var header = document.createElement("div");
|
|
219
219
|
header.id = "lvc-header";
|
|
220
|
+
var identity = document.createElement("div");
|
|
221
|
+
identity.id = "lvc-identity";
|
|
222
|
+
if (config.avatarUrl) {
|
|
223
|
+
var avatar = document.createElement("img");
|
|
224
|
+
avatar.id = "lvc-avatar";
|
|
225
|
+
avatar.src = config.avatarUrl;
|
|
226
|
+
avatar.alt = "";
|
|
227
|
+
avatar.referrerPolicy = "no-referrer";
|
|
228
|
+
avatar.addEventListener("error", function () { avatar.remove(); });
|
|
229
|
+
identity.appendChild(avatar);
|
|
230
|
+
}
|
|
220
231
|
var titles = document.createElement("div");
|
|
232
|
+
titles.id = "lvc-titles";
|
|
221
233
|
var title = document.createElement("strong");
|
|
222
234
|
title.textContent = config.appName;
|
|
223
235
|
var subtitle = document.createElement("span");
|
|
224
236
|
subtitle.textContent = config.labels.replyTime;
|
|
225
237
|
titles.appendChild(title);
|
|
226
238
|
titles.appendChild(subtitle);
|
|
227
|
-
|
|
239
|
+
identity.appendChild(titles);
|
|
240
|
+
header.appendChild(identity);
|
|
228
241
|
|
|
229
242
|
var actions = document.createElement("div");
|
|
230
243
|
actions.id = "lvc-actions";
|
|
@@ -1219,6 +1232,10 @@
|
|
|
1219
1232
|
"#lvc-header{position:relative;z-index:2;flex-shrink:0;display:flex;align-items:flex-start;" +
|
|
1220
1233
|
"justify-content:space-between;gap:8px;padding:14px 16px;background:var(--lvc-accent);" +
|
|
1221
1234
|
"color:var(--lvc-accent-text)}" +
|
|
1235
|
+
"#lvc-identity{display:flex;align-items:center;gap:10px;min-width:0}" +
|
|
1236
|
+
"#lvc-avatar{width:44px;height:44px;flex:0 0 44px;border-radius:50%;object-fit:cover;" +
|
|
1237
|
+
"border:2px solid rgba(255,255,255,.35);background:rgba(255,255,255,.15)}" +
|
|
1238
|
+
"#lvc-titles{min-width:0}" +
|
|
1222
1239
|
"#lvc-header strong{display:block;font-size:15px}" +
|
|
1223
1240
|
"#lvc-header span{display:block;font-size:12px;opacity:.85;margin-top:2px}" +
|
|
1224
1241
|
"#lvc-actions{display:flex;align-items:center;gap:2px}" +
|
data/lib/livechat/widget.rb
CHANGED
|
@@ -58,8 +58,8 @@ module Livechat
|
|
|
58
58
|
# nonce gets refused; a same-origin src is covered by `'self'` on every
|
|
59
59
|
# visit. `nonce:` is still stamped for hosts whose script-src has no
|
|
60
60
|
# 'self'; pass nil when the app has no nonce.
|
|
61
|
-
def snippet(locale:, authenticated:, nonce: nil)
|
|
62
|
-
json = config_json(locale:, authenticated:)
|
|
61
|
+
def snippet(locale:, authenticated:, avatar_url: nil, nonce: nil)
|
|
62
|
+
json = config_json(locale:, authenticated:, avatar_url:)
|
|
63
63
|
nonce_attr = nonce ? %( nonce="#{nonce}") : ''
|
|
64
64
|
src = "#{Livechat.config.mount_path.chomp('/')}/widget.js?v=#{fingerprint}"
|
|
65
65
|
|
|
@@ -67,7 +67,7 @@ module Livechat
|
|
|
67
67
|
%(<script src="#{src}" defer#{nonce_attr} data-livechat-widget></script>)
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
def config_json(locale:, authenticated:)
|
|
70
|
+
def config_json(locale:, authenticated:, avatar_url: nil)
|
|
71
71
|
config = Livechat.config
|
|
72
72
|
payload = {
|
|
73
73
|
endpoint: config.widget_endpoint,
|
|
@@ -76,6 +76,7 @@ module Livechat
|
|
|
76
76
|
authenticated: authenticated ? true : false,
|
|
77
77
|
launcher: config.show_launcher ? true : false,
|
|
78
78
|
appName: Livechat.app_name,
|
|
79
|
+
avatarUrl: avatar_url,
|
|
79
80
|
accentColor: config.accent_color,
|
|
80
81
|
attachments: Livechat.attachments_enabled?,
|
|
81
82
|
maxAttachments: config.max_attachments,
|
data/lib/livechat.rb
CHANGED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
namespace :livechat do
|
|
4
|
+
desc 'Create or refresh livechat demo conversations'
|
|
5
|
+
task seed_demo: :environment do
|
|
6
|
+
conversations = Livechat::Seeds.load!
|
|
7
|
+
puts "Seeded #{conversations.size} livechat demo conversations."
|
|
8
|
+
end
|
|
9
|
+
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: livechat
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Yaroslav Shmarov
|
|
@@ -97,9 +97,11 @@ files:
|
|
|
97
97
|
- lib/livechat/dashboard.js
|
|
98
98
|
- lib/livechat/engine.rb
|
|
99
99
|
- lib/livechat/notifications.rb
|
|
100
|
+
- lib/livechat/seeds.rb
|
|
100
101
|
- lib/livechat/version.rb
|
|
101
102
|
- lib/livechat/widget.js
|
|
102
103
|
- lib/livechat/widget.rb
|
|
104
|
+
- lib/tasks/livechat_tasks.rake
|
|
103
105
|
homepage: https://github.com/yshmarov/livechat
|
|
104
106
|
licenses:
|
|
105
107
|
- MIT
|