livechat 0.6.8 → 0.7.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a2ad48b9206502c979af98f3733631b26075e08efe04c9044e0e5969d21bc7f3
4
- data.tar.gz: b40e4efc2e6a014c34104abd860bafc41a330eebd863ad6f8e4663a41bc280fa
3
+ metadata.gz: d8510b0623f63c828acb19577dc5383d2541a245e5cda9ee667b1dba3f3a5b19
4
+ data.tar.gz: 3ae2d82e86cd025e3a750bbf583e3362273bc1b4c2b5888432b45e6445c754fc
5
5
  SHA512:
6
- metadata.gz: 19c4dc8d3f683af308f34df8341d5e8221d434bad217e0291a34a1da799afe8029e1e423dbf549d48c341cf7114c7fb14336fef47c7049cd087992d9d0492138
7
- data.tar.gz: 4459c416ea3a0ed27422847f41bbef950f11420d448c44ac5b02af0e5cca257eae0ec42e33d7cb5e7cd5f9d6c2d84ba35c6b94b92eb18c68f90cc132375da258
6
+ metadata.gz: 1c6186e6fc58c2b3b9c4732e8f25dcc07e358698ac5c31ebf60cdb8d08bf27cd50ff27e3e67b5c28713c4f14fc9eb323e55620926132ab76ca08c36829dd4eb9
7
+ data.tar.gz: fce349af0d40f1b1fd547b6cf442d8f81acec08ff425f5e105326149fce925c6c594161a3350b924ed42e947be079257776783effa2820eb732f6ea11886f56f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.1
4
+
5
+ - Removed the translucent border and background from the customer-facing
6
+ avatar so app logos sit cleanly on the widget header.
7
+
8
+ ## 0.7.0
9
+
10
+ - Added `config.avatar_url`, accepting a URL or per-request callable, to show
11
+ an optional customer-facing avatar in the visitor widget header. Failed
12
+ images disappear cleanly, and external image requests omit the referrer.
13
+ - Added `bin/rails livechat:seed_demo` for idempotent sample conversations in
14
+ development and demo environments.
15
+
3
16
  ## 0.6.8
4
17
 
5
18
  - 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
@@ -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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Livechat
4
- VERSION = '0.6.8'
4
+ VERSION = '0.7.1'
5
5
  end
@@ -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
- header.appendChild(titles);
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,9 @@
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
+ "#lvc-titles{min-width:0}" +
1222
1238
  "#lvc-header strong{display:block;font-size:15px}" +
1223
1239
  "#lvc-header span{display:block;font-size:12px;opacity:.85;margin-top:2px}" +
1224
1240
  "#lvc-actions{display:flex;align-items:center;gap:2px}" +
@@ -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
@@ -4,6 +4,7 @@ require 'livechat/version'
4
4
  require 'livechat/configuration'
5
5
  require 'livechat/widget'
6
6
  require 'livechat/notifications'
7
+ require 'livechat/seeds'
7
8
  require 'livechat/engine'
8
9
 
9
10
  # Live chat for Rails. A floating widget lets visitors — signed-in or
@@ -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.6.8
4
+ version: 0.7.1
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