Merge branch 'release/1.12.0' into master
This commit is contained in:
commit
1aaced3027
@ -18,7 +18,7 @@ FORCE_SSL=false
|
|||||||
# true : default option, allows sign ups
|
# true : default option, allows sign ups
|
||||||
# false : disables all the end points related to sign ups
|
# false : disables all the end points related to sign ups
|
||||||
# api_only: disables the UI for signup, but you can create sign ups via the account apis
|
# api_only: disables the UI for signup, but you can create sign ups via the account apis
|
||||||
ENABLE_ACCOUNT_SIGNUP=true
|
ENABLE_ACCOUNT_SIGNUP=false
|
||||||
|
|
||||||
# Redis config
|
# Redis config
|
||||||
REDIS_URL=redis://redis:6379
|
REDIS_URL=redis://redis:6379
|
||||||
@ -110,6 +110,12 @@ SLACK_CLIENT_SECRET=
|
|||||||
## Mobile app env variables
|
## Mobile app env variables
|
||||||
IOS_APP_ID=6C953F3RX2.com.chatwoot.app
|
IOS_APP_ID=6C953F3RX2.com.chatwoot.app
|
||||||
|
|
||||||
|
|
||||||
|
### Smart App Banner
|
||||||
|
# https://developer.apple.com/library/archive/documentation/AppleApplications/Reference/SafariWebContent/PromotingAppswithAppBanners/PromotingAppswithAppBanners.html
|
||||||
|
# You can find your app-id in https://itunesconnect.apple.com
|
||||||
|
#IOS_APP_IDENTIFIER=1495796682
|
||||||
|
|
||||||
## Push Notification
|
## Push Notification
|
||||||
## generate a new key value here : https://d3v.one/vapid-key-generator/
|
## generate a new key value here : https://d3v.one/vapid-key-generator/
|
||||||
# VAPID_PUBLIC_KEY=
|
# VAPID_PUBLIC_KEY=
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -60,3 +60,6 @@ package-lock.json
|
|||||||
|
|
||||||
# cypress
|
# cypress
|
||||||
test/cypress/videos/*
|
test/cypress/videos/*
|
||||||
|
|
||||||
|
/config/master.key
|
||||||
|
/config/*.enc
|
||||||
@ -11,6 +11,8 @@ Metrics/ClassLength:
|
|||||||
Max: 125
|
Max: 125
|
||||||
Exclude:
|
Exclude:
|
||||||
- 'app/models/conversation.rb'
|
- 'app/models/conversation.rb'
|
||||||
|
- 'app/mailers/conversation_reply_mailer.rb'
|
||||||
|
- 'app/models/message.rb'
|
||||||
RSpec/ExampleLength:
|
RSpec/ExampleLength:
|
||||||
Max: 25
|
Max: 25
|
||||||
Style/Documentation:
|
Style/Documentation:
|
||||||
@ -48,6 +50,7 @@ Rails/ApplicationController:
|
|||||||
- 'app/controllers/dashboard_controller.rb'
|
- 'app/controllers/dashboard_controller.rb'
|
||||||
- 'app/controllers/widget_tests_controller.rb'
|
- 'app/controllers/widget_tests_controller.rb'
|
||||||
- 'app/controllers/widgets_controller.rb'
|
- 'app/controllers/widgets_controller.rb'
|
||||||
|
- 'app/controllers/platform_controller.rb'
|
||||||
Style/ClassAndModuleChildren:
|
Style/ClassAndModuleChildren:
|
||||||
EnforcedStyle: compact
|
EnforcedStyle: compact
|
||||||
Exclude:
|
Exclude:
|
||||||
|
|||||||
@ -6,13 +6,6 @@
|
|||||||
# Note that changes in the inspected code, or installation of new
|
# Note that changes in the inspected code, or installation of new
|
||||||
# versions of RuboCop, may require this file to be generated again.
|
# versions of RuboCop, may require this file to be generated again.
|
||||||
|
|
||||||
# Offense count: 1
|
|
||||||
# Configuration parameters: EnforcedStyle.
|
|
||||||
# SupportedStyles: native, lf, crlf
|
|
||||||
Layout/EndOfLine:
|
|
||||||
Exclude:
|
|
||||||
- 'deploy/after_restart.rb'
|
|
||||||
|
|
||||||
# Offense count: 1
|
# Offense count: 1
|
||||||
Lint/DuplicateMethods:
|
Lint/DuplicateMethods:
|
||||||
Exclude:
|
Exclude:
|
||||||
|
|||||||
7
Gemfile
7
Gemfile
@ -29,6 +29,8 @@ gem 'flag_shih_tzu'
|
|||||||
gem 'haikunator'
|
gem 'haikunator'
|
||||||
# Template parsing safetly
|
# Template parsing safetly
|
||||||
gem 'liquid'
|
gem 'liquid'
|
||||||
|
# Parse Markdown to HTML
|
||||||
|
gem 'redcarpet'
|
||||||
|
|
||||||
##-- for active storage --##
|
##-- for active storage --##
|
||||||
gem 'aws-sdk-s3', require: false
|
gem 'aws-sdk-s3', require: false
|
||||||
@ -85,6 +87,8 @@ gem 'sentry-raven'
|
|||||||
|
|
||||||
##-- background job processing --##
|
##-- background job processing --##
|
||||||
gem 'sidekiq'
|
gem 'sidekiq'
|
||||||
|
# We want cron jobs
|
||||||
|
gem 'sidekiq-cron'
|
||||||
|
|
||||||
##-- Push notification service --##
|
##-- Push notification service --##
|
||||||
gem 'fcm'
|
gem 'fcm'
|
||||||
@ -96,6 +100,9 @@ gem 'geocoder'
|
|||||||
# to parse maxmind db
|
# to parse maxmind db
|
||||||
gem 'maxminddb'
|
gem 'maxminddb'
|
||||||
|
|
||||||
|
# to create db triggers
|
||||||
|
gem 'hairtrigger'
|
||||||
|
|
||||||
group :development do
|
group :development do
|
||||||
gem 'annotate'
|
gem 'annotate'
|
||||||
gem 'bullet'
|
gem 'bullet'
|
||||||
|
|||||||
53
Gemfile.lock
53
Gemfile.lock
@ -115,13 +115,14 @@ GEM
|
|||||||
descendants_tracker (~> 0.0.4)
|
descendants_tracker (~> 0.0.4)
|
||||||
ice_nine (~> 0.11.0)
|
ice_nine (~> 0.11.0)
|
||||||
thread_safe (~> 0.3, >= 0.3.1)
|
thread_safe (~> 0.3, >= 0.3.1)
|
||||||
azure-storage-blob (2.0.0)
|
azure-storage-blob (2.0.1)
|
||||||
azure-storage-common (~> 2.0)
|
azure-storage-common (~> 2.0)
|
||||||
nokogiri (~> 1.10.4)
|
nokogiri (~> 1.11.0.rc2)
|
||||||
azure-storage-common (2.0.1)
|
azure-storage-common (2.0.2)
|
||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
faraday_middleware (~> 1.0.0.rc1)
|
faraday_middleware (~> 1.0.0.rc1)
|
||||||
nokogiri (~> 1.10.4)
|
net-http-persistent (~> 4.0)
|
||||||
|
nokogiri (~> 1.11.0.rc2)
|
||||||
barnes (0.0.8)
|
barnes (0.0.8)
|
||||||
multi_json (~> 1)
|
multi_json (~> 1)
|
||||||
statsd-ruby (~> 1.1)
|
statsd-ruby (~> 1.1)
|
||||||
@ -181,6 +182,8 @@ GEM
|
|||||||
railties (>= 3.2)
|
railties (>= 3.2)
|
||||||
equalizer (0.0.11)
|
equalizer (0.0.11)
|
||||||
erubi (1.9.0)
|
erubi (1.9.0)
|
||||||
|
et-orbi (1.2.4)
|
||||||
|
tzinfo
|
||||||
execjs (2.7.0)
|
execjs (2.7.0)
|
||||||
facebook-messenger (1.5.0)
|
facebook-messenger (1.5.0)
|
||||||
httparty (~> 0.13, >= 0.13.7)
|
httparty (~> 0.13, >= 0.13.7)
|
||||||
@ -198,9 +201,12 @@ GEM
|
|||||||
faraday (~> 1.0)
|
faraday (~> 1.0)
|
||||||
fcm (1.0.2)
|
fcm (1.0.2)
|
||||||
faraday (~> 1.0.0)
|
faraday (~> 1.0.0)
|
||||||
ffi (1.13.1)
|
ffi (1.14.2)
|
||||||
flag_shih_tzu (0.3.23)
|
flag_shih_tzu (0.3.23)
|
||||||
foreman (0.87.2)
|
foreman (0.87.2)
|
||||||
|
fugit (1.4.1)
|
||||||
|
et-orbi (~> 1.1, >= 1.1.8)
|
||||||
|
raabro (~> 1.4)
|
||||||
geocoder (1.6.3)
|
geocoder (1.6.3)
|
||||||
gli (2.19.2)
|
gli (2.19.2)
|
||||||
globalid (0.4.2)
|
globalid (0.4.2)
|
||||||
@ -236,6 +242,10 @@ GEM
|
|||||||
groupdate (5.1.0)
|
groupdate (5.1.0)
|
||||||
activesupport (>= 5)
|
activesupport (>= 5)
|
||||||
haikunator (1.1.0)
|
haikunator (1.1.0)
|
||||||
|
hairtrigger (0.2.23)
|
||||||
|
activerecord (>= 5.0, < 7)
|
||||||
|
ruby2ruby (~> 2.4)
|
||||||
|
ruby_parser (~> 3.10)
|
||||||
hana (1.3.6)
|
hana (1.3.6)
|
||||||
hashdiff (1.0.1)
|
hashdiff (1.0.1)
|
||||||
hashie (4.1.0)
|
hashie (4.1.0)
|
||||||
@ -281,7 +291,7 @@ GEM
|
|||||||
letter_opener (1.7.0)
|
letter_opener (1.7.0)
|
||||||
launchy (~> 2.2)
|
launchy (~> 2.2)
|
||||||
liquid (4.0.3)
|
liquid (4.0.3)
|
||||||
listen (3.2.1)
|
listen (3.3.3)
|
||||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||||
rb-inotify (~> 0.9, >= 0.9.10)
|
rb-inotify (~> 0.9, >= 0.9.10)
|
||||||
loofah (2.7.0)
|
loofah (2.7.0)
|
||||||
@ -300,7 +310,7 @@ GEM
|
|||||||
mimemagic (0.3.5)
|
mimemagic (0.3.5)
|
||||||
mini_magick (4.10.1)
|
mini_magick (4.10.1)
|
||||||
mini_mime (1.0.2)
|
mini_mime (1.0.2)
|
||||||
mini_portile2 (2.4.0)
|
mini_portile2 (2.5.0)
|
||||||
minitest (5.14.2)
|
minitest (5.14.2)
|
||||||
momentjs-rails (2.20.1)
|
momentjs-rails (2.20.1)
|
||||||
railties (>= 3.1)
|
railties (>= 3.1)
|
||||||
@ -308,10 +318,13 @@ GEM
|
|||||||
multi_json (1.15.0)
|
multi_json (1.15.0)
|
||||||
multi_xml (0.6.0)
|
multi_xml (0.6.0)
|
||||||
multipart-post (2.1.1)
|
multipart-post (2.1.1)
|
||||||
|
net-http-persistent (4.0.0)
|
||||||
|
connection_pool (~> 2.2)
|
||||||
netrc (0.11.0)
|
netrc (0.11.0)
|
||||||
nio4r (2.5.4)
|
nio4r (2.5.4)
|
||||||
nokogiri (1.10.10)
|
nokogiri (1.11.0)
|
||||||
mini_portile2 (~> 2.4.0)
|
mini_portile2 (~> 2.5.0)
|
||||||
|
racc (~> 1.4)
|
||||||
oauth (0.5.4)
|
oauth (0.5.4)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
os (1.1.1)
|
os (1.1.1)
|
||||||
@ -329,6 +342,8 @@ GEM
|
|||||||
nio4r (~> 2.0)
|
nio4r (~> 2.0)
|
||||||
pundit (2.1.0)
|
pundit (2.1.0)
|
||||||
activesupport (>= 3.0.0)
|
activesupport (>= 3.0.0)
|
||||||
|
raabro (1.4.0)
|
||||||
|
racc (1.5.2)
|
||||||
rack (2.2.3)
|
rack (2.2.3)
|
||||||
rack-cache (1.12.0)
|
rack-cache (1.12.0)
|
||||||
rack (>= 0.4)
|
rack (>= 0.4)
|
||||||
@ -369,6 +384,7 @@ GEM
|
|||||||
rb-fsevent (0.10.4)
|
rb-fsevent (0.10.4)
|
||||||
rb-inotify (0.10.1)
|
rb-inotify (0.10.1)
|
||||||
ffi (~> 1.0)
|
ffi (~> 1.0)
|
||||||
|
redcarpet (3.5.1)
|
||||||
redis (4.2.1)
|
redis (4.2.1)
|
||||||
redis-namespace (1.8.0)
|
redis-namespace (1.8.0)
|
||||||
redis (>= 3.0.4)
|
redis (>= 3.0.4)
|
||||||
@ -422,13 +438,19 @@ GEM
|
|||||||
parser (>= 2.7.1.4)
|
parser (>= 2.7.1.4)
|
||||||
rubocop-performance (1.7.1)
|
rubocop-performance (1.7.1)
|
||||||
rubocop (>= 0.82.0)
|
rubocop (>= 0.82.0)
|
||||||
rubocop-rails (2.7.1)
|
rubocop-rails (2.8.1)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
rack (>= 1.1)
|
rack (>= 1.1)
|
||||||
rubocop (>= 0.87.0)
|
rubocop (>= 0.87.0)
|
||||||
rubocop-rspec (1.43.2)
|
rubocop-rspec (1.43.2)
|
||||||
rubocop (~> 0.87)
|
rubocop (~> 0.87)
|
||||||
ruby-progressbar (1.10.1)
|
ruby-progressbar (1.10.1)
|
||||||
|
ruby2ruby (2.4.4)
|
||||||
|
ruby_parser (~> 3.1)
|
||||||
|
sexp_processor (~> 4.6)
|
||||||
|
ruby_parser (3.15.0)
|
||||||
|
rubocop (>= 0.87.0)
|
||||||
|
sexp_processor (~> 4.9)
|
||||||
safe_yaml (1.0.5)
|
safe_yaml (1.0.5)
|
||||||
sass (3.7.4)
|
sass (3.7.4)
|
||||||
sass-listen (~> 4.0.0)
|
sass-listen (~> 4.0.0)
|
||||||
@ -454,12 +476,16 @@ GEM
|
|||||||
semantic_range (2.3.0)
|
semantic_range (2.3.0)
|
||||||
sentry-raven (3.0.3)
|
sentry-raven (3.0.3)
|
||||||
faraday (>= 1.0)
|
faraday (>= 1.0)
|
||||||
|
sexp_processor (4.15.1)
|
||||||
shoulda-matchers (4.4.1)
|
shoulda-matchers (4.4.1)
|
||||||
activesupport (>= 4.2.0)
|
activesupport (>= 4.2.0)
|
||||||
sidekiq (6.1.1)
|
sidekiq (6.1.1)
|
||||||
connection_pool (>= 2.2.2)
|
connection_pool (>= 2.2.2)
|
||||||
rack (~> 2.0)
|
rack (~> 2.0)
|
||||||
redis (>= 4.2.0)
|
redis (>= 4.2.0)
|
||||||
|
sidekiq-cron (1.2.0)
|
||||||
|
fugit (~> 1.1)
|
||||||
|
sidekiq (>= 4.2.1)
|
||||||
signet (0.14.0)
|
signet (0.14.0)
|
||||||
addressable (~> 2.3)
|
addressable (~> 2.3)
|
||||||
faraday (>= 0.17.3, < 2.0)
|
faraday (>= 0.17.3, < 2.0)
|
||||||
@ -506,7 +532,7 @@ GEM
|
|||||||
nokogiri (>= 1.6, < 2.0)
|
nokogiri (>= 1.6, < 2.0)
|
||||||
twitty (0.1.1)
|
twitty (0.1.1)
|
||||||
oauth
|
oauth
|
||||||
tzinfo (1.2.7)
|
tzinfo (1.2.8)
|
||||||
thread_safe (~> 0.1)
|
thread_safe (~> 0.1)
|
||||||
tzinfo-data (1.2020.1)
|
tzinfo-data (1.2020.1)
|
||||||
tzinfo (>= 1.0.0)
|
tzinfo (>= 1.0.0)
|
||||||
@ -549,7 +575,7 @@ GEM
|
|||||||
websocket-extensions (>= 0.1.0)
|
websocket-extensions (>= 0.1.0)
|
||||||
websocket-extensions (0.1.5)
|
websocket-extensions (0.1.5)
|
||||||
wisper (2.0.0)
|
wisper (2.0.0)
|
||||||
zeitwerk (2.4.0)
|
zeitwerk (2.4.1)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
ruby
|
ruby
|
||||||
@ -584,6 +610,7 @@ DEPENDENCIES
|
|||||||
google-cloud-storage
|
google-cloud-storage
|
||||||
groupdate
|
groupdate
|
||||||
haikunator
|
haikunator
|
||||||
|
hairtrigger
|
||||||
hashie
|
hashie
|
||||||
jbuilder
|
jbuilder
|
||||||
json_refs!
|
json_refs!
|
||||||
@ -602,6 +629,7 @@ DEPENDENCIES
|
|||||||
pundit
|
pundit
|
||||||
rack-cors
|
rack-cors
|
||||||
rails
|
rails
|
||||||
|
redcarpet
|
||||||
redis
|
redis
|
||||||
redis-namespace
|
redis-namespace
|
||||||
redis-rack-cache
|
redis-rack-cache
|
||||||
@ -618,6 +646,7 @@ DEPENDENCIES
|
|||||||
sentry-raven
|
sentry-raven
|
||||||
shoulda-matchers
|
shoulda-matchers
|
||||||
sidekiq
|
sidekiq
|
||||||
|
sidekiq-cron
|
||||||
simplecov (= 0.17.1)
|
simplecov (= 0.17.1)
|
||||||
slack-ruby-client
|
slack-ruby-client
|
||||||
spring
|
spring
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
|||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2017-2020 ThoughtWoot Inc.
|
Copyright (c) 2017-2021 Chatwoot Inc.
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
@ -64,7 +64,7 @@ Detailed documentation is available at [www.chatwoot.com/help-center](https://ww
|
|||||||
|
|
||||||
### Translation process
|
### Translation process
|
||||||
|
|
||||||
The translation process for Chatwoot web and mobile app is managed at [https://translate.chatwoot.com](https://translate.chatwoot.com) using Crowdin. Please read the [translation guide](https://www.chatwoot/docs/contributing/translating-chatwoot-to-your-language) for contributing to Chatwoot.
|
The translation process for Chatwoot web and mobile app is managed at [https://translate.chatwoot.com](https://translate.chatwoot.com) using Crowdin. Please read the [translation guide](https://www.chatwoot.com/docs/contributing/translating-chatwoot-to-your-language) for contributing to Chatwoot.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@ -98,4 +98,4 @@ Thanks goes to all these [wonderful people](https://www.chatwoot.com/docs/contri
|
|||||||
<a href="https://github.com/chatwoot/chatwoot/graphs/contributors"><img src="https://opencollective.com/chatwoot/contributors.svg?width=890&button=false" /></a>
|
<a href="https://github.com/chatwoot/chatwoot/graphs/contributors"><img src="https://opencollective.com/chatwoot/contributors.svg?width=890&button=false" /></a>
|
||||||
|
|
||||||
|
|
||||||
*Chatwoot* © 2017-2020, ThoughtWoot Inc - Released under the MIT License.
|
*Chatwoot* © 2017-2021, Chatwoot Inc - Released under the MIT License.
|
||||||
|
|||||||
5
app.json
5
app.json
@ -11,7 +11,10 @@
|
|||||||
"rails",
|
"rails",
|
||||||
"vue"
|
"vue"
|
||||||
],
|
],
|
||||||
"success_url": "/app/login",
|
"success_url": "/",
|
||||||
|
"scripts": {
|
||||||
|
"postdeploy": "bundle exec rake db:seed"
|
||||||
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"SECRET_TOKEN": {
|
"SECRET_TOKEN": {
|
||||||
"description": "A secret key for verifying the integrity of signed cookies.",
|
"description": "A secret key for verifying the integrity of signed cookies.",
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
class AccountBuilder
|
class AccountBuilder
|
||||||
include CustomExceptions::Account
|
include CustomExceptions::Account
|
||||||
pattr_initialize [:account_name!, :email!, :confirmed!, :user]
|
pattr_initialize [:account_name!, :email!, :confirmed!, :user, :user_full_name, :user_password]
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
if @user.nil?
|
if @user.nil?
|
||||||
@ -15,7 +15,6 @@ class AccountBuilder
|
|||||||
end
|
end
|
||||||
[@user, @account]
|
[@user, @account]
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
@account&.destroy
|
|
||||||
puts e.inspect
|
puts e.inspect
|
||||||
raise e
|
raise e
|
||||||
end
|
end
|
||||||
@ -27,7 +26,7 @@ class AccountBuilder
|
|||||||
if address.valid? # && !address.disposable?
|
if address.valid? # && !address.disposable?
|
||||||
true
|
true
|
||||||
else
|
else
|
||||||
raise InvalidEmail.new(valid: address.valid?) # , disposable: address.disposable?})
|
raise InvalidEmail.new(valid: address.valid?)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -61,18 +60,13 @@ class AccountBuilder
|
|||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
def email_to_name(email)
|
|
||||||
name = email[/[^@]+/]
|
|
||||||
name.split('.').map(&:capitalize).join(' ')
|
|
||||||
end
|
|
||||||
|
|
||||||
def create_user
|
def create_user
|
||||||
password = SecureRandom.alphanumeric(12)
|
password = user_password || SecureRandom.alphanumeric(12)
|
||||||
|
|
||||||
@user = User.new(email: @email,
|
@user = User.new(email: @email,
|
||||||
password: password,
|
password: password,
|
||||||
password_confirmation: password,
|
password_confirmation: password,
|
||||||
name: email_to_name(@email))
|
name: @user_full_name)
|
||||||
@user.confirm if @confirmed
|
@user.confirm if @confirmed
|
||||||
@user.save!
|
@user.save!
|
||||||
end
|
end
|
||||||
|
|||||||
@ -5,7 +5,7 @@ class ContactBuilder
|
|||||||
contact_inbox = inbox.contact_inboxes.find_by(source_id: source_id)
|
contact_inbox = inbox.contact_inboxes.find_by(source_id: source_id)
|
||||||
return contact_inbox if contact_inbox
|
return contact_inbox if contact_inbox
|
||||||
|
|
||||||
build_contact
|
build_contact_inbox
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@ -26,16 +26,29 @@ class ContactBuilder
|
|||||||
::ContactAvatarJob.perform_later(contact, contact_attributes[:avatar_url]) if contact_attributes[:avatar_url]
|
::ContactAvatarJob.perform_later(contact, contact_attributes[:avatar_url]) if contact_attributes[:avatar_url]
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_contact
|
def create_contact
|
||||||
ActiveRecord::Base.transaction do
|
account.contacts.create!(
|
||||||
contact = account.contacts.create!(
|
name: contact_attributes[:name],
|
||||||
name: contact_attributes[:name],
|
phone_number: contact_attributes[:phone_number],
|
||||||
phone_number: contact_attributes[:phone_number],
|
email: contact_attributes[:email],
|
||||||
email: contact_attributes[:email],
|
identifier: contact_attributes[:identifier],
|
||||||
identifier: contact_attributes[:identifier],
|
additional_attributes: contact_attributes[:additional_attributes]
|
||||||
additional_attributes: contact_attributes[:additional_attributes]
|
)
|
||||||
)
|
end
|
||||||
|
|
||||||
|
def find_contact
|
||||||
|
contact = nil
|
||||||
|
|
||||||
|
contact = account.contacts.find_by(identifier: contact_attributes[:identifier]) if contact_attributes[:identifier].present?
|
||||||
|
|
||||||
|
contact ||= account.contacts.find_by(email: contact_attributes[:email]) if contact_attributes[:email].present?
|
||||||
|
|
||||||
|
contact
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_contact_inbox
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
|
contact = find_contact || create_contact
|
||||||
contact_inbox = create_contact_inbox(contact)
|
contact_inbox = create_contact_inbox(contact)
|
||||||
update_contact_avatar(contact)
|
update_contact_avatar(contact)
|
||||||
contact_inbox
|
contact_inbox
|
||||||
|
|||||||
@ -0,0 +1,13 @@
|
|||||||
|
class Api::V1::Accounts::Contacts::LabelsController < Api::V1::Accounts::BaseController
|
||||||
|
include LabelConcern
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def model
|
||||||
|
@model ||= Current.account.contacts.find(permitted_params[:contact_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def permitted_params
|
||||||
|
params.permit(:contact_id, labels: [])
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -1,11 +1,13 @@
|
|||||||
class Api::V1::Accounts::Conversations::LabelsController < Api::V1::Accounts::Conversations::BaseController
|
class Api::V1::Accounts::Conversations::LabelsController < Api::V1::Accounts::Conversations::BaseController
|
||||||
def create
|
include LabelConcern
|
||||||
@conversation.update_labels(params[:labels])
|
|
||||||
@labels = @conversation.label_list
|
private
|
||||||
|
|
||||||
|
def model
|
||||||
|
@model ||= @conversation
|
||||||
end
|
end
|
||||||
|
|
||||||
# all labels of the current conversation
|
def permitted_params
|
||||||
def index
|
params.permit(:conversation_id, labels: [])
|
||||||
@labels = @conversation.label_list
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -11,9 +11,24 @@ class Api::V1::Accounts::Conversations::MessagesController < Api::V1::Accounts::
|
|||||||
render_could_not_create_error(e.message)
|
render_could_not_create_error(e.message)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
ActiveRecord::Base.transaction do
|
||||||
|
message.update!(content: I18n.t('conversations.messages.deleted'), deleted: true)
|
||||||
|
message.attachments.destroy_all
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def message
|
||||||
|
@message ||= @conversation.messages.find(permitted_params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
def message_finder
|
def message_finder
|
||||||
@message_finder ||= MessageFinder.new(@conversation, params)
|
@message_finder ||= MessageFinder.new(@conversation, params)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def permitted_params
|
||||||
|
params.permit(:id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -86,7 +86,8 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
|||||||
account_id: Current.account.id,
|
account_id: Current.account.id,
|
||||||
inbox_id: @contact_inbox.inbox_id,
|
inbox_id: @contact_inbox.inbox_id,
|
||||||
contact_id: @contact_inbox.contact_id,
|
contact_id: @contact_inbox.contact_id,
|
||||||
contact_inbox_id: @contact_inbox.id
|
contact_inbox_id: @contact_inbox.id,
|
||||||
|
additional_attributes: params[:additional_attributes]
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@ -3,10 +3,12 @@ class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseContro
|
|||||||
|
|
||||||
before_action :fetch_notification, only: [:update]
|
before_action :fetch_notification, only: [:update]
|
||||||
before_action :set_primary_actor, only: [:read_all]
|
before_action :set_primary_actor, only: [:read_all]
|
||||||
|
before_action :set_current_page, only: [:index]
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@unread_count = current_user.notifications.where(account_id: current_account.id, read_at: nil).count
|
@unread_count = current_user.notifications.where(account_id: current_account.id, read_at: nil).count
|
||||||
@notifications = current_user.notifications.where(account_id: current_account.id).page params[:page]
|
@count = notifications.count
|
||||||
|
@notifications = notifications.page @current_page
|
||||||
end
|
end
|
||||||
|
|
||||||
def read_all
|
def read_all
|
||||||
@ -25,6 +27,11 @@ class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseContro
|
|||||||
render json: @notification
|
render json: @notification
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def unread_count
|
||||||
|
@unread_count = current_user.notifications.where(account_id: current_account.id, read_at: nil).count
|
||||||
|
render json: @unread_count
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def set_primary_actor
|
def set_primary_actor
|
||||||
@ -37,4 +44,12 @@ class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseContro
|
|||||||
def fetch_notification
|
def fetch_notification
|
||||||
@notification = current_user.notifications.find(params[:id])
|
@notification = current_user.notifications.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def set_current_page
|
||||||
|
@current_page = params[:page] || 1
|
||||||
|
end
|
||||||
|
|
||||||
|
def notifications
|
||||||
|
@notifications ||= current_user.notifications.where(account_id: current_account.id)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
24
app/controllers/api/v1/accounts/team_members_controller.rb
Normal file
24
app/controllers/api/v1/accounts/team_members_controller.rb
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
class Api::V1::Accounts::TeamMembersController < Api::V1::Accounts::BaseController
|
||||||
|
before_action :fetch_team
|
||||||
|
before_action :check_authorization
|
||||||
|
|
||||||
|
def index
|
||||||
|
@team_members = @team.team_members.map(&:user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
record = @team.team_members.find_or_create_by(user_id: params[:user_id])
|
||||||
|
@team_member = record.user
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@team.team_members.find_by(user_id: params[:user_id])&.destroy
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def fetch_team
|
||||||
|
@team = Current.account.teams.find(params[:team_id])
|
||||||
|
end
|
||||||
|
end
|
||||||
34
app/controllers/api/v1/accounts/teams_controller.rb
Normal file
34
app/controllers/api/v1/accounts/teams_controller.rb
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
class Api::V1::Accounts::TeamsController < Api::V1::Accounts::BaseController
|
||||||
|
before_action :fetch_team, only: [:show, :update, :destroy]
|
||||||
|
before_action :check_authorization
|
||||||
|
|
||||||
|
def index
|
||||||
|
@teams = Current.account.teams
|
||||||
|
end
|
||||||
|
|
||||||
|
def show; end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@team = Current.account.teams.new(team_params)
|
||||||
|
@team.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@team.update!(team_params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@team.destroy
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def fetch_team
|
||||||
|
@team = Current.account.teams.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def team_params
|
||||||
|
params.require(:team).permit(:name, :description, :allow_auto_assign)
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -16,6 +16,7 @@ class Api::V1::AccountsController < Api::BaseController
|
|||||||
def create
|
def create
|
||||||
@user, @account = AccountBuilder.new(
|
@user, @account = AccountBuilder.new(
|
||||||
account_name: account_params[:account_name],
|
account_name: account_params[:account_name],
|
||||||
|
user_full_name: account_params[:user_full_name],
|
||||||
email: account_params[:email],
|
email: account_params[:email],
|
||||||
confirmed: confirmed?,
|
confirmed: confirmed?,
|
||||||
user: current_user
|
user: current_user
|
||||||
@ -29,6 +30,7 @@ class Api::V1::AccountsController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
@latest_chatwoot_version = ::Redis::Alfred.get(::Redis::Alfred::LATEST_CHATWOOT_VERSION)
|
||||||
render 'api/v1/accounts/show.json'
|
render 'api/v1/accounts/show.json'
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -54,7 +56,7 @@ class Api::V1::AccountsController < Api::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def account_params
|
def account_params
|
||||||
params.permit(:account_name, :email, :name, :locale, :domain, :support_email, :auto_resolve_duration)
|
params.permit(:account_name, :email, :name, :locale, :domain, :support_email, :auto_resolve_duration, :user_full_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def check_signup_enabled
|
def check_signup_enabled
|
||||||
|
|||||||
@ -23,7 +23,8 @@ class Api::V1::ProfilesController < Api::BaseController
|
|||||||
:password,
|
:password,
|
||||||
:password_confirmation,
|
:password_confirmation,
|
||||||
:avatar,
|
:avatar,
|
||||||
:availability
|
:availability,
|
||||||
|
ui_settings: {}
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -7,7 +7,12 @@ class Api::V1::Widget::BaseController < ApplicationController
|
|||||||
private
|
private
|
||||||
|
|
||||||
def conversations
|
def conversations
|
||||||
@conversations = @contact_inbox.conversations.where(inbox_id: auth_token_params[:inbox_id])
|
if @contact_inbox.hmac_verified?
|
||||||
|
verified_contact_inbox_ids = @contact.contact_inboxes.where(inbox_id: auth_token_params[:inbox_id], hmac_verified: true).map(&:id)
|
||||||
|
@conversations = @contact.conversations.where(contact_inbox_id: verified_contact_inbox_ids)
|
||||||
|
else
|
||||||
|
@conversations = @contact_inbox.conversations.where(inbox_id: auth_token_params[:inbox_id])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def conversation
|
def conversation
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
class Api::V1::Widget::ContactsController < Api::V1::Widget::BaseController
|
class Api::V1::Widget::ContactsController < Api::V1::Widget::BaseController
|
||||||
def update
|
def update
|
||||||
|
process_hmac
|
||||||
contact_identify_action = ContactIdentifyAction.new(
|
contact_identify_action = ContactIdentifyAction.new(
|
||||||
contact: @contact,
|
contact: @contact,
|
||||||
params: permitted_params.to_h.deep_symbolize_keys
|
params: permitted_params.to_h.deep_symbolize_keys
|
||||||
@ -9,7 +10,22 @@ class Api::V1::Widget::ContactsController < Api::V1::Widget::BaseController
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def process_hmac
|
||||||
|
return if params[:identifier_hash].blank?
|
||||||
|
raise StandardError, 'HMAC failed: Invalid Identifer Hash Provided' unless valid_hmac?
|
||||||
|
|
||||||
|
@contact_inbox.update(hmac_verified: true)
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_hmac?
|
||||||
|
params[:identifier_hash] == OpenSSL::HMAC.hexdigest(
|
||||||
|
'sha256',
|
||||||
|
@web_widget.hmac_token,
|
||||||
|
params[:identifier].to_s
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
def permitted_params
|
def permitted_params
|
||||||
params.permit(:website_token, :identifier, :email, :name, :avatar_url, custom_attributes: {})
|
params.permit(:website_token, :identifier, :identifier_hash, :email, :name, :avatar_url, custom_attributes: {})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@ -54,6 +54,8 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def conversation_params
|
def conversation_params
|
||||||
|
# FIXME: typo referrer in additional attributes
|
||||||
|
# will probably require a migration.
|
||||||
{
|
{
|
||||||
account_id: inbox.account_id,
|
account_id: inbox.account_id,
|
||||||
inbox_id: inbox.id,
|
inbox_id: inbox.id,
|
||||||
|
|||||||
@ -23,7 +23,7 @@ class ApplicationController < ActionController::Base
|
|||||||
render_unauthorized('You are not authorized to do this action')
|
render_unauthorized('You are not authorized to do this action')
|
||||||
ensure
|
ensure
|
||||||
# to address the thread variable leak issues in Puma/Thin webserver
|
# to address the thread variable leak issues in Puma/Thin webserver
|
||||||
Current.user = nil
|
Current.reset
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_current_user
|
def set_current_user
|
||||||
|
|||||||
10
app/controllers/concerns/label_concern.rb
Normal file
10
app/controllers/concerns/label_concern.rb
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
module LabelConcern
|
||||||
|
def create
|
||||||
|
model.update_labels(permitted_params[:labels])
|
||||||
|
@labels = model.label_list
|
||||||
|
end
|
||||||
|
|
||||||
|
def index
|
||||||
|
@labels = model.label_list
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -1,5 +1,10 @@
|
|||||||
class DashboardController < ActionController::Base
|
class DashboardController < ActionController::Base
|
||||||
|
include SwitchLocale
|
||||||
|
|
||||||
before_action :set_global_config
|
before_action :set_global_config
|
||||||
|
around_action :switch_locale
|
||||||
|
before_action :ensure_installation_onboarding, only: [:index]
|
||||||
|
|
||||||
layout 'vueapp'
|
layout 'vueapp'
|
||||||
|
|
||||||
def index; end
|
def index; end
|
||||||
@ -20,4 +25,8 @@ class DashboardController < ActionController::Base
|
|||||||
APP_VERSION: Chatwoot.config[:version]
|
APP_VERSION: Chatwoot.config[:version]
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def ensure_installation_onboarding
|
||||||
|
redirect_to '/installation/onboarding' if ::Redis::Alfred.get(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
36
app/controllers/installation/onboarding_controller.rb
Normal file
36
app/controllers/installation/onboarding_controller.rb
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
class Installation::OnboardingController < ApplicationController
|
||||||
|
before_action :ensure_installation_onboarding
|
||||||
|
|
||||||
|
def index; end
|
||||||
|
|
||||||
|
def create
|
||||||
|
begin
|
||||||
|
AccountBuilder.new(
|
||||||
|
account_name: onboarding_params.dig(:user, :company),
|
||||||
|
user_full_name: onboarding_params.dig(:user, :name),
|
||||||
|
email: onboarding_params.dig(:user, :email),
|
||||||
|
user_password: params.dig(:user, :password),
|
||||||
|
confirmed: true
|
||||||
|
).perform
|
||||||
|
rescue StandardError => e
|
||||||
|
redirect_to '/', flash: { error: e.message } and return
|
||||||
|
end
|
||||||
|
finish_onboarding
|
||||||
|
redirect_to '/'
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def onboarding_params
|
||||||
|
params.permit(:subscribe_to_updates, user: [:name, :company, :email])
|
||||||
|
end
|
||||||
|
|
||||||
|
def finish_onboarding
|
||||||
|
::Redis::Alfred.delete(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING)
|
||||||
|
ChatwootHub.register_instance(onboarding_params) if onboarding_params[:subscribe_to_updates]
|
||||||
|
end
|
||||||
|
|
||||||
|
def ensure_installation_onboarding
|
||||||
|
redirect_to '/' unless ::Redis::Alfred.get(::Redis::Alfred::CHATWOOT_INSTALLATION_ONBOARDING)
|
||||||
|
end
|
||||||
|
end
|
||||||
29
app/controllers/platform/api/v1/account_users_controller.rb
Normal file
29
app/controllers/platform/api/v1/account_users_controller.rb
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
class Platform::Api::V1::AccountUsersController < PlatformController
|
||||||
|
before_action :set_resource
|
||||||
|
before_action :validate_platform_app_permissible
|
||||||
|
|
||||||
|
def index
|
||||||
|
render json: @resource.account_users
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@account_user = @resource.account_users.find_or_initialize_by(user_id: account_user_params[:user_id])
|
||||||
|
@account_user.update!(account_user_params)
|
||||||
|
render json: @account_user
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@resource.account_users.find_by(user_id: account_user_params[:user_id])&.destroy
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_resource
|
||||||
|
@resource = Account.find(params[:account_id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_user_params
|
||||||
|
params.permit(:user_id, :role)
|
||||||
|
end
|
||||||
|
end
|
||||||
32
app/controllers/platform/api/v1/accounts_controller.rb
Normal file
32
app/controllers/platform/api/v1/accounts_controller.rb
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
class Platform::Api::V1::AccountsController < PlatformController
|
||||||
|
def create
|
||||||
|
@resource = Account.new(account_params)
|
||||||
|
@resource.save!
|
||||||
|
@platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource)
|
||||||
|
render json: @resource
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
render json: @resource
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@resource.update!(account_params)
|
||||||
|
render json: @resource
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
# TODO: obfusicate account
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_resource
|
||||||
|
@resource = Account.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def account_params
|
||||||
|
params.permit(:name)
|
||||||
|
end
|
||||||
|
end
|
||||||
43
app/controllers/platform/api/v1/users_controller.rb
Normal file
43
app/controllers/platform/api/v1/users_controller.rb
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
class Platform::Api::V1::UsersController < PlatformController
|
||||||
|
# ref: https://stackoverflow.com/a/45190318/939299
|
||||||
|
# set resource is called for other actions already in platform controller
|
||||||
|
# we want to add login to that chain as well
|
||||||
|
before_action(only: [:login]) { set_resource }
|
||||||
|
before_action(only: [:login]) { validate_platform_app_permissible }
|
||||||
|
|
||||||
|
def create
|
||||||
|
@resource = (User.find_by(email: user_params[:email]) || User.new(user_params))
|
||||||
|
@resource.confirm
|
||||||
|
@resource.save!
|
||||||
|
@platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource)
|
||||||
|
render json: @resource
|
||||||
|
end
|
||||||
|
|
||||||
|
def login
|
||||||
|
render json: { url: "#{ENV['FRONTEND_URL']}/app/login?email=#{@resource.email}&sso_auth_token=#{@resource.generate_sso_auth_token}" }
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
render json: @resource
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@resource.update!(user_params)
|
||||||
|
render json: @resource
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
# TODO: obfusicate user
|
||||||
|
head :ok
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def set_resource
|
||||||
|
@resource = User.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def user_params
|
||||||
|
params.permit(:name, :email, :password)
|
||||||
|
end
|
||||||
|
end
|
||||||
37
app/controllers/platform_controller.rb
Normal file
37
app/controllers/platform_controller.rb
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
class PlatformController < ActionController::Base
|
||||||
|
protect_from_forgery with: :null_session
|
||||||
|
|
||||||
|
before_action :ensure_access_token
|
||||||
|
before_action :set_platform_app
|
||||||
|
before_action :set_resource, only: [:update, :show, :destroy]
|
||||||
|
before_action :validate_platform_app_permissible, only: [:update, :show, :destroy]
|
||||||
|
|
||||||
|
def show; end
|
||||||
|
|
||||||
|
def update; end
|
||||||
|
|
||||||
|
def destroy; end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def ensure_access_token
|
||||||
|
token = request.headers[:api_access_token] || request.headers[:HTTP_API_ACCESS_TOKEN]
|
||||||
|
@access_token = AccessToken.find_by(token: token) if token.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_platform_app
|
||||||
|
@platform_app = @access_token.owner if @access_token && @access_token.owner.is_a?(PlatformApp)
|
||||||
|
render json: { error: 'Invalid access_token' }, status: :unauthorized if @platform_app.blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_resource
|
||||||
|
# set @resource in your controller
|
||||||
|
raise 'Overwrite this method your controller'
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_platform_app_permissible
|
||||||
|
return if @platform_app.platform_app_permissibles.find_by(permissible: @resource)
|
||||||
|
|
||||||
|
render json: { error: 'Non permissible resource' }, status: :unauthorized
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
class SuperAdmin::InstallationConfigsController < SuperAdmin::ApplicationController
|
||||||
|
# Overwrite any of the RESTful controller actions to implement custom behavior
|
||||||
|
# For example, you may want to send an email after a foo is updated.
|
||||||
|
#
|
||||||
|
# def update
|
||||||
|
# super
|
||||||
|
# send_foo_updated_email(requested_resource)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# Override this method to specify custom lookup behavior.
|
||||||
|
# This will be used to set the resource for the `show`, `edit`, and `update`
|
||||||
|
# actions.
|
||||||
|
#
|
||||||
|
# def find_resource(param)
|
||||||
|
# Foo.find_by!(slug: param)
|
||||||
|
# end
|
||||||
|
|
||||||
|
# The result of this lookup will be available as `requested_resource`
|
||||||
|
|
||||||
|
# Override this if you have certain roles that require a subset
|
||||||
|
# this will be used to set the records shown on the `index` action.
|
||||||
|
#
|
||||||
|
def scoped_resource
|
||||||
|
resource_class.editable
|
||||||
|
end
|
||||||
|
|
||||||
|
# Override `resource_params` if you want to transform the submitted
|
||||||
|
# data before it's persisted. For example, the following would turn all
|
||||||
|
# empty values into nil values. It uses other APIs such as `resource_class`
|
||||||
|
# and `dashboard`:
|
||||||
|
#
|
||||||
|
# def resource_params
|
||||||
|
# params.require(resource_class.model_name.param_key).
|
||||||
|
# permit(dashboard.permitted_attributes).
|
||||||
|
# transform_values { |value| value == "" ? nil : value }
|
||||||
|
# end
|
||||||
|
|
||||||
|
def resource_params
|
||||||
|
params.require(:installation_config)
|
||||||
|
.permit(:name, :value)
|
||||||
|
.transform_values { |value| value == '' ? nil : value }.merge(locked: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
# See https://administrate-prototype.herokuapp.com/customizing_controller_actions
|
||||||
|
# for more information
|
||||||
|
end
|
||||||
66
app/dashboards/installation_config_dashboard.rb
Normal file
66
app/dashboards/installation_config_dashboard.rb
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
require 'administrate/base_dashboard'
|
||||||
|
|
||||||
|
class InstallationConfigDashboard < Administrate::BaseDashboard
|
||||||
|
# ATTRIBUTE_TYPES
|
||||||
|
# a hash that describes the type of each of the model's fields.
|
||||||
|
#
|
||||||
|
# Each different type represents an Administrate::Field object,
|
||||||
|
# which determines how the attribute is displayed
|
||||||
|
# on pages throughout the dashboard.
|
||||||
|
ATTRIBUTE_TYPES = {
|
||||||
|
id: Field::Number,
|
||||||
|
name: Field::String,
|
||||||
|
value: SerializedField,
|
||||||
|
created_at: Field::DateTime,
|
||||||
|
updated_at: Field::DateTime
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
# COLLECTION_ATTRIBUTES
|
||||||
|
# an array of attributes that will be displayed on the model's index page.
|
||||||
|
#
|
||||||
|
# By default, it's limited to four items to reduce clutter on index pages.
|
||||||
|
# Feel free to add, remove, or rearrange items.
|
||||||
|
COLLECTION_ATTRIBUTES = %i[
|
||||||
|
id
|
||||||
|
name
|
||||||
|
value
|
||||||
|
created_at
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# SHOW_PAGE_ATTRIBUTES
|
||||||
|
# an array of attributes that will be displayed on the model's show page.
|
||||||
|
SHOW_PAGE_ATTRIBUTES = %i[
|
||||||
|
id
|
||||||
|
name
|
||||||
|
value
|
||||||
|
created_at
|
||||||
|
updated_at
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# FORM_ATTRIBUTES
|
||||||
|
# an array of attributes that will be displayed
|
||||||
|
# on the model's form (`new` and `edit`) pages.
|
||||||
|
FORM_ATTRIBUTES = %i[
|
||||||
|
name
|
||||||
|
value
|
||||||
|
].freeze
|
||||||
|
|
||||||
|
# COLLECTION_FILTERS
|
||||||
|
# a hash that defines filters that can be used while searching via the search
|
||||||
|
# field of the dashboard.
|
||||||
|
#
|
||||||
|
# For example to add an option to search for open resources by typing "open:"
|
||||||
|
# in the search field:
|
||||||
|
#
|
||||||
|
# COLLECTION_FILTERS = {
|
||||||
|
# open: ->(resources) { resources.where(open: true) }
|
||||||
|
# }.freeze
|
||||||
|
COLLECTION_FILTERS = {}.freeze
|
||||||
|
|
||||||
|
# Overwrite this method to customize how installation configs are displayed
|
||||||
|
# across all pages of the admin dashboard.
|
||||||
|
#
|
||||||
|
# def display_resource(installation_config)
|
||||||
|
# "InstallationConfig ##{installation_config.id}"
|
||||||
|
# end
|
||||||
|
end
|
||||||
15
app/fields/serialized_field.rb
Normal file
15
app/fields/serialized_field.rb
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
require 'administrate/field/base'
|
||||||
|
|
||||||
|
class SerializedField < Administrate::Field::Base
|
||||||
|
def to_s
|
||||||
|
hash? ? data.as_json : data.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
def hash?
|
||||||
|
data.is_a? Hash
|
||||||
|
end
|
||||||
|
|
||||||
|
def array?
|
||||||
|
data.is_a? Array
|
||||||
|
end
|
||||||
|
end
|
||||||
@ -26,7 +26,8 @@ export default {
|
|||||||
const fetchPromise = new Promise((resolve, reject) => {
|
const fetchPromise = new Promise((resolve, reject) => {
|
||||||
axios
|
axios
|
||||||
.post(urlData.url, {
|
.post(urlData.url, {
|
||||||
account_name: creds.name,
|
account_name: creds.accountName.trim(),
|
||||||
|
user_full_name: creds.fullName.trim(),
|
||||||
email: creds.email,
|
email: creds.email,
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
@ -139,6 +140,12 @@ export default {
|
|||||||
return axios.put(endPoints('profileUpdate').url, formData);
|
return axios.put(endPoints('profileUpdate').url, formData);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateUISettings({ uiSettings }) {
|
||||||
|
return axios.put(endPoints('profileUpdate').url, {
|
||||||
|
profile: { ui_settings: uiSettings },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
updateAvailability({ availability }) {
|
updateAvailability({ availability }) {
|
||||||
return axios.put(endPoints('profileUpdate').url, {
|
return axios.put(endPoints('profileUpdate').url, {
|
||||||
profile: { availability },
|
profile: { availability },
|
||||||
|
|||||||
@ -7,11 +7,26 @@ class MessageApi extends ApiClient {
|
|||||||
super('conversations', { accountScoped: true });
|
super('conversations', { accountScoped: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
create({ conversationId, message, private: isPrivate, contentAttributes }) {
|
create({
|
||||||
return axios.post(`${this.url}/${conversationId}/messages`, {
|
conversationId,
|
||||||
content: message,
|
message,
|
||||||
private: isPrivate,
|
private: isPrivate,
|
||||||
content_attributes: contentAttributes,
|
contentAttributes,
|
||||||
|
echo_id: echoId,
|
||||||
|
file,
|
||||||
|
}) {
|
||||||
|
const formData = new FormData();
|
||||||
|
if (file) formData.append('attachments[]', file, file.name);
|
||||||
|
if (message) formData.append('content', message);
|
||||||
|
if (contentAttributes)
|
||||||
|
formData.append('content_attributes', JSON.stringify(contentAttributes));
|
||||||
|
|
||||||
|
formData.append('private', isPrivate);
|
||||||
|
formData.append('echo_id', echoId);
|
||||||
|
return axios({
|
||||||
|
method: 'post',
|
||||||
|
url: `${this.url}/${conversationId}/messages`,
|
||||||
|
data: formData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,17 +35,6 @@ class MessageApi extends ApiClient {
|
|||||||
params: { before },
|
params: { before },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
sendAttachment([conversationId, { file, isPrivate = false }]) {
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('attachments[]', file, file.name);
|
|
||||||
formData.append('private', isPrivate);
|
|
||||||
return axios({
|
|
||||||
method: 'post',
|
|
||||||
url: `${this.url}/${conversationId}/messages`,
|
|
||||||
data: formData,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new MessageApi();
|
export default new MessageApi();
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
.button {
|
.button {
|
||||||
font-family: $body-font-family;
|
font-family: $body-font-family;
|
||||||
font-weight: $font-weight-medium;
|
font-weight: $font-weight-medium;
|
||||||
@ -35,6 +34,7 @@ code {
|
|||||||
|
|
||||||
&.hljs {
|
&.hljs {
|
||||||
background: $color-background;
|
background: $color-background;
|
||||||
|
border-radius: var(--border-radius-large);
|
||||||
padding: $space-two;
|
padding: $space-two;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -236,18 +236,18 @@ $breadcrumbs-item-slash: true;
|
|||||||
// 11. Button
|
// 11. Button
|
||||||
// ----------
|
// ----------
|
||||||
|
|
||||||
$button-padding: $space-one $space-normal;
|
$button-padding: var(--space-one) var(--space-slab);
|
||||||
$button-margin: 0 0 $global-margin 0;
|
$button-margin: 0 0 $global-margin 0;
|
||||||
$button-fill: solid;
|
$button-fill: solid;
|
||||||
$button-background: $primary-color;
|
$button-background: $primary-color;
|
||||||
$button-background-hover: scale-color($button-background, $lightness: -15%);
|
$button-background-hover: scale-color($button-background, $lightness: -15%);
|
||||||
$button-color: $white;
|
$button-color: $white;
|
||||||
$button-color-alt: $white;
|
$button-color-alt: $white;
|
||||||
$button-radius: $global-radius;
|
$button-radius: var(--border-radius-normal);
|
||||||
$button-sizes: (tiny: $font-size-micro,
|
$button-sizes: (tiny: var(--font-size-nano),
|
||||||
small: $font-size-mini,
|
small: var(--font-size-mini),
|
||||||
default: $font-size-default,
|
default: var(--font-size-small),
|
||||||
large: $font-size-large);
|
large: var(--font-size-medium));
|
||||||
$button-palette: $foundation-palette;
|
$button-palette: $foundation-palette;
|
||||||
$button-opacity-disabled: 0.25;
|
$button-opacity-disabled: 0.25;
|
||||||
$button-background-hover-lightness: -20%;
|
$button-background-hover-lightness: -20%;
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
@import 'views/settings/inbox';
|
@import 'views/settings/inbox';
|
||||||
@import 'views/settings/channel';
|
@import 'views/settings/channel';
|
||||||
@import 'views/settings/integrations';
|
@import 'views/settings/integrations';
|
||||||
@import 'views/signup';
|
|
||||||
|
|
||||||
@import 'plugins/multiselect';
|
@import 'plugins/multiselect';
|
||||||
@import 'plugins/dropdown';
|
@import 'plugins/dropdown';
|
||||||
|
@import '@chatwoot/prosemirror-schema/src/woot-editor.css';
|
||||||
|
|||||||
@ -3,10 +3,12 @@
|
|||||||
@import 'shared/assets/stylesheets/spacing';
|
@import 'shared/assets/stylesheets/spacing';
|
||||||
@import 'shared/assets/stylesheets/font-size';
|
@import 'shared/assets/stylesheets/font-size';
|
||||||
@import 'shared/assets/stylesheets/font-weights';
|
@import 'shared/assets/stylesheets/font-weights';
|
||||||
|
@import 'shared/assets/stylesheets/shadows';
|
||||||
|
@import 'shared/assets/stylesheets/border-radius';
|
||||||
@import 'variables';
|
@import 'variables';
|
||||||
|
|
||||||
@import '~spinkit/scss/spinners/7-three-bounce';
|
@import '~spinkit/scss/spinners/7-three-bounce';
|
||||||
@import '~ionicons/scss/ionicons';
|
@import '~shared/assets/stylesheets/ionicons';
|
||||||
|
|
||||||
@import 'mixins';
|
@import 'mixins';
|
||||||
@import 'foundation-settings';
|
@import 'foundation-settings';
|
||||||
|
|||||||
@ -1,15 +1,36 @@
|
|||||||
@import '../variables';
|
@import '../variables';
|
||||||
|
|
||||||
.superadmin-body {
|
.superadmin-body {
|
||||||
background: $color-background;
|
background: var(--color-background);
|
||||||
|
|
||||||
|
.hero--title {
|
||||||
|
font-size: var(--font-size-mega);
|
||||||
|
font-weight: var(--font-weight-light);
|
||||||
|
margin-top: var(--space-large);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.alert-box {
|
.alert-box {
|
||||||
background-color: $alert-color;
|
background-color: var(--r-500);
|
||||||
border-radius: 5px;
|
border-radius: 5px;
|
||||||
color: $color-white;
|
color: var(--color-white);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
margin-bottom: 14px;
|
margin-bottom: 14px;
|
||||||
padding: 10px;
|
padding: 10px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.update-subscription--checkbox {
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
input {
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-right: var(--space-one);
|
||||||
|
}
|
||||||
|
|
||||||
|
div {
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
line-height: 1.5;
|
||||||
|
margin-bottom: var(--space-normal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
@import 'shared/assets/fonts/inter';
|
@import 'shared/assets/fonts/inter';
|
||||||
@import '../variables';
|
@import '../variables';
|
||||||
@import '~ionicons/scss/ionicons';
|
@import '~shared/assets/stylesheets/ionicons';
|
||||||
|
|||||||
@ -1,94 +0,0 @@
|
|||||||
.signup {
|
|
||||||
// margin-top: $space-larger*1.2;
|
|
||||||
|
|
||||||
.signup--hero {
|
|
||||||
margin-bottom: $space-larger * 1.5;
|
|
||||||
|
|
||||||
.hero--logo {
|
|
||||||
width: 180px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero--title {
|
|
||||||
margin-top: $space-large;
|
|
||||||
font-weight: $font-weight-light;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hero--sub {
|
|
||||||
font-size: $font-size-medium;
|
|
||||||
color: $medium-gray;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup--features {
|
|
||||||
list-style-type: none;
|
|
||||||
font-size: $font-size-medium;
|
|
||||||
|
|
||||||
> li {
|
|
||||||
padding: $space-slab;
|
|
||||||
|
|
||||||
> i {
|
|
||||||
margin-right: $space-two;
|
|
||||||
font-size: $font-size-large;
|
|
||||||
|
|
||||||
&.beer {
|
|
||||||
color: #dfb63b;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.report {
|
|
||||||
color: #2196f3;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.canned {
|
|
||||||
color: #1cad22;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.uptime {
|
|
||||||
color: #a753b5;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.secure {
|
|
||||||
color: #607d8b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.signup--box {
|
|
||||||
@include elegant-card;
|
|
||||||
padding: $space-large;
|
|
||||||
|
|
||||||
label {
|
|
||||||
font-size: $font-size-default;
|
|
||||||
color: $color-gray;
|
|
||||||
|
|
||||||
input {
|
|
||||||
padding: $space-slab;
|
|
||||||
height: $space-larger;
|
|
||||||
font-size: $font-size-default;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
|
||||||
font-size: $font-size-small
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sigin--footer {
|
|
||||||
padding: $space-medium;
|
|
||||||
font-size: $font-size-default;
|
|
||||||
|
|
||||||
> a {
|
|
||||||
font-weight: $font-weight-bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.accept--terms {
|
|
||||||
font-size: $font-size-mini;
|
|
||||||
text-align: center;
|
|
||||||
@include margin($zero);
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-size: $font-size-mini;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,6 +1,21 @@
|
|||||||
.button {
|
.button {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
|
|
||||||
|
&.button--emoji {
|
||||||
|
background: var(--b-50);
|
||||||
|
border: 1px solid var(--color-border-light);
|
||||||
|
border-radius: var(--border-radius-large);
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
margin-right: var(--space-small);
|
||||||
|
padding: var(--space-small);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--b-200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
&.icon {
|
&.icon {
|
||||||
padding-left: $space-normal;
|
padding-left: $space-normal;
|
||||||
padding-right: $space-normal;
|
padding-right: $space-normal;
|
||||||
|
|||||||
@ -72,7 +72,7 @@ $resolve-button-width: 13.2rem;
|
|||||||
|
|
||||||
.button.resolve--button {
|
.button.resolve--button {
|
||||||
@include flex-align($x: center, $y: middle);
|
@include flex-align($x: center, $y: middle);
|
||||||
|
font-size: var(--font-size-default);
|
||||||
width: $resolve-button-width;
|
width: $resolve-button-width;
|
||||||
|
|
||||||
>.icon {
|
>.icon {
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
@include margin($zero);
|
@include margin($zero);
|
||||||
background: $color-woot;
|
background: $color-woot;
|
||||||
border-radius: $space-one;
|
border-radius: $space-one;
|
||||||
color: $color-white;
|
color: var(--white);
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
font-weight: $font-weight-normal;
|
font-weight: $font-weight-normal;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -11,20 +11,12 @@
|
|||||||
.message-text__wrap {
|
.message-text__wrap {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
color: $color-white;
|
color: var(--white);
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.message-text {
|
|
||||||
&::after {
|
|
||||||
content: ' \00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0';
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
@ -95,17 +87,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.content-box {
|
.content-box {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.conversation-wrap {
|
.conversation-wrap {
|
||||||
@include background-gray;
|
|
||||||
@include margin(0);
|
@include margin(0);
|
||||||
@include border-normal-left;
|
@include border-normal-left;
|
||||||
|
background: var(--color-background-light);
|
||||||
|
|
||||||
.current-chat {
|
.current-chat {
|
||||||
@include flex;
|
@include flex;
|
||||||
@ -145,10 +135,9 @@
|
|||||||
@include flex-weight(1);
|
@include flex-weight(1);
|
||||||
@include margin($zero);
|
@include margin($zero);
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
// Firefox flexbox fix
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
margin-bottom: $space-small;
|
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
padding-bottom: var(--space-normal);
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +160,7 @@
|
|||||||
@include elegant-card;
|
@include elegant-card;
|
||||||
@include round-corner;
|
@include round-corner;
|
||||||
background: $color-woot;
|
background: $color-woot;
|
||||||
color: $color-white;
|
color: var(--white);
|
||||||
font-size: $font-size-mini;
|
font-size: $font-size-mini;
|
||||||
font-weight: $font-weight-medium;
|
font-weight: $font-weight-medium;
|
||||||
margin: $space-one auto;
|
margin: $space-one auto;
|
||||||
@ -201,6 +190,10 @@
|
|||||||
color: $color-body;
|
color: $color-body;
|
||||||
margin-right: auto;
|
margin-right: auto;
|
||||||
|
|
||||||
|
&.is-image {
|
||||||
|
border-radius: var(--border-radius-large);
|
||||||
|
}
|
||||||
|
|
||||||
.link {
|
.link {
|
||||||
color: $color-primary-dark;
|
color: $color-primary-dark;
|
||||||
}
|
}
|
||||||
@ -218,6 +211,7 @@
|
|||||||
color: $color-primary-dark;
|
color: $color-primary-dark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
+.right {
|
+.right {
|
||||||
@ -247,7 +241,6 @@
|
|||||||
background: lighten($warning-color, 32%);
|
background: lighten($warning-color, 32%);
|
||||||
border: 1px solid lighten($warning-color, 15%);
|
border: 1px solid lighten($warning-color, 15%);
|
||||||
color: $color-heading;
|
color: $color-heading;
|
||||||
padding-right: $space-large;
|
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
&::before {
|
&::before {
|
||||||
@ -258,6 +251,10 @@
|
|||||||
top: $space-smaller + $space-micro;
|
top: $space-smaller + $space-micro;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.is-image {
|
||||||
|
border-radius: var(--border-radius-large);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+.left {
|
+.left {
|
||||||
@ -297,30 +294,15 @@
|
|||||||
border-radius: $space-smaller;
|
border-radius: $space-smaller;
|
||||||
font-size: $font-size-small;
|
font-size: $font-size-small;
|
||||||
|
|
||||||
p {
|
.message-text__wrap {
|
||||||
color: $color-heading;
|
display: inline-block;
|
||||||
margin-bottom: $zero;
|
|
||||||
|
|
||||||
.ion-person {
|
|
||||||
color: $color-body;
|
|
||||||
font-size: $font-size-default;
|
|
||||||
margin-right: $space-small;
|
|
||||||
position: relative;
|
|
||||||
top: $space-micro;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-text__wrap {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-text {
|
|
||||||
&::after {
|
|
||||||
content: ' \00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0\00a0';
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.activity-wrap .message-text__wrap {
|
||||||
|
.text-content p {
|
||||||
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,7 +323,7 @@
|
|||||||
.typing-indicator {
|
.typing-indicator {
|
||||||
@include elegant-card;
|
@include elegant-card;
|
||||||
@include round-corner;
|
@include round-corner;
|
||||||
background: $color-white;
|
background: var(--white);
|
||||||
color: $color-light-gray;
|
color: $color-light-gray;
|
||||||
font-size: $font-size-mini;
|
font-size: $font-size-mini;
|
||||||
font-weight: $font-weight-bold;
|
font-weight: $font-weight-bold;
|
||||||
@ -354,3 +336,65 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.left .bubble .text-content {
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
color: var(--color-body);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--color-woot);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border-left-color: var(--s-300);
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: var(--s-300);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right .bubble .text-content {
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
color: var(--white);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--white);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
border-left-color: var(--w-100);
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: var(--w-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pre code {
|
||||||
|
background: var(--color-background);
|
||||||
|
}
|
||||||
|
|
||||||
|
p:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -2,12 +2,13 @@
|
|||||||
#{$all-text-inputs},
|
#{$all-text-inputs},
|
||||||
select,
|
select,
|
||||||
.multiselect > .multiselect__tags {
|
.multiselect > .multiselect__tags {
|
||||||
@include thin-border(darken(get-color(alert), 25%));
|
@include thin-border(var(--r-400));
|
||||||
}
|
}
|
||||||
|
|
||||||
.message {
|
.message {
|
||||||
color: darken(get-color(alert), 25%);
|
color: var(--r-400);
|
||||||
display: block;
|
display: block;
|
||||||
|
font-size: var(--font-size-small);
|
||||||
font-weight: $font-weight-normal;
|
font-weight: $font-weight-normal;
|
||||||
margin-bottom: $space-one;
|
margin-bottom: $space-one;
|
||||||
margin-top: -$space-normal;
|
margin-top: -$space-normal;
|
||||||
|
|||||||
@ -1,28 +1,13 @@
|
|||||||
.reply-box {
|
.reply-box {
|
||||||
@include light-shadow;
|
|
||||||
border-bottom: 0;
|
|
||||||
border-radius: $space-small;
|
|
||||||
margin: $space-normal;
|
|
||||||
margin-top: 0;
|
|
||||||
max-height: $space-mega * 3;
|
|
||||||
transition: box-shadow .35s $swift-ease-out-function,
|
transition: box-shadow .35s $swift-ease-out-function,
|
||||||
height 2s $swift-ease-out-function;
|
height 2s $swift-ease-out-function;
|
||||||
|
|
||||||
|
|
||||||
&.is-focused {
|
&.is-focused {
|
||||||
@include shadow;
|
box-shadow: var(--shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.reply-box__top {
|
.reply-box__top {
|
||||||
@include flex;
|
|
||||||
@include flex-align($x: left, $y: middle);
|
|
||||||
@include padding($space-one $space-normal);
|
|
||||||
@include background-white;
|
|
||||||
@include margin(0);
|
|
||||||
border-top-left-radius: $space-small;
|
|
||||||
border-top-right-radius: $space-small;
|
|
||||||
position: relative;
|
|
||||||
|
|
||||||
.canned {
|
.canned {
|
||||||
@include elegant-card;
|
@include elegant-card;
|
||||||
background: $color-white;
|
background: $color-white;
|
||||||
@ -41,19 +26,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&.is-active {
|
|
||||||
border-bottom-left-radius: $space-small;
|
|
||||||
border-bottom-right-radius: $space-small;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-private {
|
|
||||||
background: lighten($warning-color, 38%);
|
|
||||||
|
|
||||||
>input {
|
|
||||||
background: lighten($warning-color, 38%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
.icon {
|
||||||
color: $medium-gray;
|
color: $medium-gray;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -65,9 +37,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-uploads>label {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.attachment {
|
.attachment {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -82,87 +51,45 @@
|
|||||||
// Override min-height : 50px in foundation
|
// Override min-height : 50px in foundation
|
||||||
//
|
//
|
||||||
max-height: $space-mega * 2.4;
|
max-height: $space-mega * 2.4;
|
||||||
min-height: 4rem;
|
min-height: 4.8rem;
|
||||||
|
padding: var(--space-normal) 0 0;
|
||||||
resize: none;
|
resize: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.reply-box__bottom {
|
&.is-private {
|
||||||
@include background-light;
|
background: var(--y-50);
|
||||||
@include flex;
|
|
||||||
@include flex-align($x: justify, $y: middle);
|
|
||||||
@include border-light-top;
|
|
||||||
border-bottom-left-radius: $space-small;
|
|
||||||
border-bottom-right-radius: $space-small;
|
|
||||||
|
|
||||||
.tabs {
|
.reply-box__top {
|
||||||
border: 0;
|
background: var(--y-50);
|
||||||
flex: 1;
|
|
||||||
padding: 0;
|
|
||||||
|
|
||||||
.tabs-title {
|
>input {
|
||||||
margin: 0;
|
background: var(--y-50);
|
||||||
transition: all .2s $swift-ease-out-function;
|
|
||||||
transition-property: color, background;
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-weight: $font-weight-medium;
|
|
||||||
padding: $space-one $space-two;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.is-private.is-active {
|
|
||||||
background: lighten($warning-color, 38%);
|
|
||||||
|
|
||||||
a {
|
|
||||||
border-bottom-color: darken($warning-color, 15%);
|
|
||||||
color: darken($warning-color, 15%);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabs-title:first-child {
|
|
||||||
border-bottom-left-radius: $space-small;
|
|
||||||
|
|
||||||
&.is-active {
|
|
||||||
@include border-light-right;
|
|
||||||
border-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
border-bottom-left-radius: $space-small;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.is-active {
|
|
||||||
@include background-white;
|
|
||||||
@include border-light-left;
|
|
||||||
@include border-light-right;
|
|
||||||
margin-top: -1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-length {
|
|
||||||
float: right;
|
|
||||||
|
|
||||||
a {
|
|
||||||
font-size: $font-size-mini;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-error {
|
|
||||||
color: $input-error-color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.send-button {
|
|
||||||
border-bottom-right-radius: $space-small;
|
|
||||||
height: 3.6rem;
|
|
||||||
padding-left: $space-two;
|
|
||||||
padding-right: $space-two;
|
|
||||||
padding-top: $space-small;
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
margin-left: $space-small;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.file-uploads>label {
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover .button--emoji {
|
||||||
|
background: var(--b-200);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-box .button--emoji.button--upload {
|
||||||
|
height: var(--space-large);
|
||||||
|
padding: 0;
|
||||||
|
width: var(--space-large);
|
||||||
|
|
||||||
|
.file-uploads {
|
||||||
|
height: 100%;
|
||||||
|
line-height: var(--space-large);
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
padding: var(--space-small);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,5 +42,9 @@ export default {
|
|||||||
font-weight: $font-weight-medium;
|
font-weight: $font-weight-medium;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.title--section {
|
||||||
|
padding-right: var(--space-large);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
class="button nice resolve--button"
|
class="button resolve--button"
|
||||||
:class="buttonClass"
|
:class="buttonClass"
|
||||||
@click="toggleStatus"
|
@click="toggleStatus"
|
||||||
>
|
>
|
||||||
|
|||||||
@ -0,0 +1,150 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div
|
||||||
|
v-for="(attachment, index) in attachments"
|
||||||
|
:key="attachment.id"
|
||||||
|
class="preview-item"
|
||||||
|
>
|
||||||
|
<div class="thumb-wrap">
|
||||||
|
<img
|
||||||
|
v-if="isTypeImage(attachment.resource.type)"
|
||||||
|
class="image-thumb"
|
||||||
|
:src="attachment.thumb"
|
||||||
|
/>
|
||||||
|
<span v-else class="attachment-thumb">
|
||||||
|
📄
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="file-name-wrap">
|
||||||
|
<span class="item">
|
||||||
|
{{ attachment.resource.name }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="file-size-wrap">
|
||||||
|
<span class="item">
|
||||||
|
{{ formatFileSize(attachment.resource.size) }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="remove-file-wrap">
|
||||||
|
<button
|
||||||
|
class="remove--attachment"
|
||||||
|
@click="() => onRemoveAttachment(index)"
|
||||||
|
>
|
||||||
|
<i class="ion-android-close"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script>
|
||||||
|
import { formatBytes } from 'dashboard/helper/files';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
props: {
|
||||||
|
attachments: {
|
||||||
|
type: Array,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
removeAttachment: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onRemoveAttachment(index) {
|
||||||
|
this.removeAttachment(index);
|
||||||
|
},
|
||||||
|
formatFileSize(size) {
|
||||||
|
return formatBytes(size, 0);
|
||||||
|
},
|
||||||
|
isTypeImage(type) {
|
||||||
|
return type.includes('image');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.preview-item {
|
||||||
|
display: flex;
|
||||||
|
padding: var(--space-slab) 0 0;
|
||||||
|
background: var(--color-background-light);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.thumb-wrap {
|
||||||
|
max-width: var(--space-jumbo);
|
||||||
|
flex-shrink: 0;
|
||||||
|
width: var(--space-medium);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.image-thumb {
|
||||||
|
width: var(--space-medium);
|
||||||
|
height: var(--space-medium);
|
||||||
|
object-fit: cover;
|
||||||
|
border-radius: var(--border-radius-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.attachment-thumb {
|
||||||
|
width: var(--space-medium);
|
||||||
|
height: var(--space-medium);
|
||||||
|
font-size: var(--font-size-medium);
|
||||||
|
text-align: center;
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name-wrap,
|
||||||
|
.file-size-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0 var(--space-smaller);
|
||||||
|
|
||||||
|
> .item {
|
||||||
|
margin: 0;
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-header {
|
||||||
|
padding: var(--space-slab) var(--space-slab) 0 var(--space-slab);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-name-wrap {
|
||||||
|
max-width: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
.item {
|
||||||
|
height: var(--space-normal);
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-size-wrap {
|
||||||
|
width: 20%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove-file-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remove--attachment {
|
||||||
|
width: var(--space-medium);
|
||||||
|
height: var(--space-medium);
|
||||||
|
border-radius: var(--space-medium);
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--color-background);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,118 @@
|
|||||||
|
<template>
|
||||||
|
<div ref="editor" class="editor-root"></div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { EditorState } from 'prosemirror-state';
|
||||||
|
import { EditorView } from 'prosemirror-view';
|
||||||
|
import {
|
||||||
|
schema,
|
||||||
|
defaultMarkdownParser,
|
||||||
|
defaultMarkdownSerializer,
|
||||||
|
} from 'prosemirror-markdown';
|
||||||
|
import { wootWriterSetup } from '@chatwoot/prosemirror-schema';
|
||||||
|
|
||||||
|
const TYPING_INDICATOR_IDLE_TIME = 4000;
|
||||||
|
|
||||||
|
const createState = (content, placeholder) =>
|
||||||
|
EditorState.create({
|
||||||
|
doc: defaultMarkdownParser.parse(content),
|
||||||
|
plugins: wootWriterSetup({ schema, placeholder }),
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'WootMessageEditor',
|
||||||
|
props: {
|
||||||
|
value: { type: String, default: '' },
|
||||||
|
placeholder: { type: String, default: '' },
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
lastValue: null,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
value(newValue) {
|
||||||
|
if (newValue !== this.lastValue) {
|
||||||
|
this.state = createState(newValue, this.placeholder);
|
||||||
|
this.view.updateState(this.state);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
created() {
|
||||||
|
this.state = createState(this.value, this.placeholder);
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.view = new EditorView(this.$refs.editor, {
|
||||||
|
state: this.state,
|
||||||
|
dispatchTransaction: tx => {
|
||||||
|
this.state = this.state.apply(tx);
|
||||||
|
this.view.updateState(this.state);
|
||||||
|
this.lastValue = defaultMarkdownSerializer.serialize(this.state.doc);
|
||||||
|
this.$emit('input', this.lastValue);
|
||||||
|
},
|
||||||
|
handleDOMEvents: {
|
||||||
|
keyup: () => {
|
||||||
|
this.onKeyup();
|
||||||
|
},
|
||||||
|
focus: () => {
|
||||||
|
this.onFocus();
|
||||||
|
},
|
||||||
|
blur: () => {
|
||||||
|
this.onBlur();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
resetTyping() {
|
||||||
|
this.$emit('typing-off');
|
||||||
|
this.idleTimer = null;
|
||||||
|
},
|
||||||
|
turnOffIdleTimer() {
|
||||||
|
if (this.idleTimer) {
|
||||||
|
clearTimeout(this.idleTimer);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onKeyup() {
|
||||||
|
if (!this.idleTimer) {
|
||||||
|
this.$emit('typing-on');
|
||||||
|
}
|
||||||
|
this.turnOffIdleTimer();
|
||||||
|
this.idleTimer = setTimeout(
|
||||||
|
() => this.resetTyping(),
|
||||||
|
TYPING_INDICATOR_IDLE_TIME
|
||||||
|
);
|
||||||
|
},
|
||||||
|
onBlur() {
|
||||||
|
this.turnOffIdleTimer();
|
||||||
|
this.resetTyping();
|
||||||
|
this.$emit('blur');
|
||||||
|
},
|
||||||
|
onFocus() {
|
||||||
|
this.$emit('focus');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss">
|
||||||
|
.ProseMirror-menubar-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
> .ProseMirror {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.editor-root {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ProseMirror-woot-style {
|
||||||
|
min-height: 8rem;
|
||||||
|
max-height: 12rem;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,188 @@
|
|||||||
|
<template>
|
||||||
|
<div class="bottom-box" :class="wrapClass">
|
||||||
|
<div class="left-wrap">
|
||||||
|
<button
|
||||||
|
class="button clear button--emoji"
|
||||||
|
:title="$t('CONVERSATION.REPLYBOX.TIP_EMOJI_ICON')"
|
||||||
|
@click="toggleEmojiPicker"
|
||||||
|
>
|
||||||
|
<emoji-or-icon icon="ion-happy-outline" emoji="😊" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="showAttachButton"
|
||||||
|
class="button clear button--emoji button--upload"
|
||||||
|
:title="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
|
||||||
|
>
|
||||||
|
<file-upload
|
||||||
|
:size="4096 * 4096"
|
||||||
|
accept="image/*, application/pdf, audio/mpeg, video/mp4, audio/ogg, text/csv"
|
||||||
|
@input-file="onFileUpload"
|
||||||
|
>
|
||||||
|
<emoji-or-icon icon="ion-android-attach" emoji="📎" />
|
||||||
|
</file-upload>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
v-if="enableRichEditor"
|
||||||
|
class="button clear button--emoji"
|
||||||
|
:title="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
|
||||||
|
@click="toggleFormatMode"
|
||||||
|
>
|
||||||
|
<emoji-or-icon icon="ion-quote" emoji="🖊️" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="right-wrap">
|
||||||
|
<button
|
||||||
|
class="button nice primary button--send"
|
||||||
|
:class="buttonClass"
|
||||||
|
@click="onSend"
|
||||||
|
>
|
||||||
|
{{ sendButtonText }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import FileUpload from 'vue-upload-component';
|
||||||
|
import EmojiOrIcon from 'shared/components/EmojiOrIcon';
|
||||||
|
|
||||||
|
import { REPLY_EDITOR_MODES } from './constants';
|
||||||
|
export default {
|
||||||
|
name: 'ReplyTopPanel',
|
||||||
|
components: { EmojiOrIcon, FileUpload },
|
||||||
|
props: {
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: REPLY_EDITOR_MODES.REPLY,
|
||||||
|
},
|
||||||
|
onSend: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
sendButtonText: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
showFileUpload: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
onFileUpload: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
showEmojiPicker: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
toggleEmojiPicker: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
isSendDisabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
setFormatMode: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
isFormatMode: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
enableRichEditor: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isNote() {
|
||||||
|
return this.mode === REPLY_EDITOR_MODES.NOTE;
|
||||||
|
},
|
||||||
|
wrapClass() {
|
||||||
|
return {
|
||||||
|
'is-note-mode': this.isNote,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
buttonClass() {
|
||||||
|
return {
|
||||||
|
'button--note': this.isNote,
|
||||||
|
'button--disabled': this.isSendDisabled,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
showAttachButton() {
|
||||||
|
return this.showFileUpload || this.isNote;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
toggleFormatMode() {
|
||||||
|
this.setFormatMode(!this.isFormatMode);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
@import '~widget/assets/scss/variables.scss';
|
||||||
|
@import '~widget/assets/scss/mixins.scss';
|
||||||
|
|
||||||
|
.bottom-box {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: var(--space-slab) var(--space-normal);
|
||||||
|
|
||||||
|
&.is-note-mode {
|
||||||
|
background: var(--y-50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.button--note {
|
||||||
|
background: var(--y-800);
|
||||||
|
color: white;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--y-700);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.button--disabled {
|
||||||
|
background: var(--b-100);
|
||||||
|
color: var(--b-400);
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--b-100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-box.is-note-mode {
|
||||||
|
.button--emoji {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--reply {
|
||||||
|
border-right: 1px solid var(--color-border-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon--font {
|
||||||
|
color: var(--s-600);
|
||||||
|
font-size: var(--font-size-default);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,156 @@
|
|||||||
|
<template>
|
||||||
|
<div class="top-box">
|
||||||
|
<div class="mode-wrap button-group">
|
||||||
|
<button
|
||||||
|
class="button clear button--reply"
|
||||||
|
:class="replyButtonClass"
|
||||||
|
@click="handleReplyClick"
|
||||||
|
>
|
||||||
|
<emoji-or-icon icon="" emoji="💬" />
|
||||||
|
{{ $t('CONVERSATION.REPLYBOX.REPLY') }}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
class="button clear button--note"
|
||||||
|
:class="noteButtonClass"
|
||||||
|
@click="handleNoteClick"
|
||||||
|
>
|
||||||
|
<emoji-or-icon icon="" emoji="📝" />
|
||||||
|
{{ $t('CONVERSATION.REPLYBOX.PRIVATE_NOTE') }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="action-wrap">
|
||||||
|
<div v-if="isMessageLengthReachingThreshold" class="tabs-title">
|
||||||
|
<span :class="charLengthClass">
|
||||||
|
{{ characterLengthWarning }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { REPLY_EDITOR_MODES, CHAR_LENGTH_WARNING } from './constants';
|
||||||
|
import EmojiOrIcon from 'shared/components/EmojiOrIcon';
|
||||||
|
export default {
|
||||||
|
name: 'ReplyTopPanel',
|
||||||
|
components: {
|
||||||
|
EmojiOrIcon,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
mode: {
|
||||||
|
type: String,
|
||||||
|
default: REPLY_EDITOR_MODES.REPLY,
|
||||||
|
},
|
||||||
|
setReplyMode: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
isMessageLengthReachingThreshold: {
|
||||||
|
type: Boolean,
|
||||||
|
default: () => false,
|
||||||
|
},
|
||||||
|
charactersRemaining: {
|
||||||
|
type: Number,
|
||||||
|
default: () => 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
replyButtonClass() {
|
||||||
|
return {
|
||||||
|
'is-active': this.mode === REPLY_EDITOR_MODES.REPLY,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
noteButtonClass() {
|
||||||
|
return {
|
||||||
|
'is-active': this.mode === REPLY_EDITOR_MODES.NOTE,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
charLengthClass() {
|
||||||
|
return this.charactersRemaining < 0 ? 'message-error' : 'message-length';
|
||||||
|
},
|
||||||
|
characterLengthWarning() {
|
||||||
|
return this.charactersRemaining < 0
|
||||||
|
? `${-this.charactersRemaining} ${CHAR_LENGTH_WARNING.NEGATIVE}`
|
||||||
|
: `${this.charactersRemaining} ${CHAR_LENGTH_WARNING.UNDER_50}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleReplyClick() {
|
||||||
|
this.setReplyMode(REPLY_EDITOR_MODES.REPLY);
|
||||||
|
},
|
||||||
|
handleNoteClick() {
|
||||||
|
this.setReplyMode(REPLY_EDITOR_MODES.NOTE);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.top-box {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
background: var(--b-100);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group {
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
font-weight: var(--font-weight-medium);
|
||||||
|
padding: var(--space-one) var(--space-normal);
|
||||||
|
margin: 0;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--reply {
|
||||||
|
border-radius: 0;
|
||||||
|
border-right: 1px solid var(--color-border);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
border-right: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--note {
|
||||||
|
border-radius: 0;
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
border-right: 1px solid var(--color-border);
|
||||||
|
background: var(--y-50);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:active {
|
||||||
|
color: var(--y-800);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button--note {
|
||||||
|
color: var(--y-900);
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-wrap {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin: 0 var(--space-normal);
|
||||||
|
font-size: var(--font-size-mini);
|
||||||
|
|
||||||
|
.message-error {
|
||||||
|
color: var(--r-600);
|
||||||
|
}
|
||||||
|
.message-length {
|
||||||
|
color: var(--s-600);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
export const REPLY_EDITOR_MODES = {
|
||||||
|
REPLY: 'REPLY',
|
||||||
|
NOTE: 'NOTE',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CHAR_LENGTH_WARNING = {
|
||||||
|
UNDER_50: 'characters remaining',
|
||||||
|
NEGATIVE: 'characters over',
|
||||||
|
};
|
||||||
@ -27,7 +27,7 @@
|
|||||||
<p v-if="lastMessageInChat" class="conversation--message">
|
<p v-if="lastMessageInChat" class="conversation--message">
|
||||||
<i v-if="messageByAgent" class="ion-ios-undo message-from-agent"></i>
|
<i v-if="messageByAgent" class="ion-ios-undo message-from-agent"></i>
|
||||||
<span v-if="lastMessageInChat.content">
|
<span v-if="lastMessageInChat.content">
|
||||||
{{ lastMessageInChat.content }}
|
{{ parsedLastMessage }}
|
||||||
</span>
|
</span>
|
||||||
<span v-else-if="!lastMessageInChat.attachments">{{ ` ` }}</span>
|
<span v-else-if="!lastMessageInChat.attachments">{{ ` ` }}</span>
|
||||||
<span v-else>
|
<span v-else>
|
||||||
@ -47,6 +47,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
||||||
|
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||||
|
|
||||||
import Thumbnail from '../Thumbnail';
|
import Thumbnail from '../Thumbnail';
|
||||||
import conversationMixin from '../../../mixins/conversations';
|
import conversationMixin from '../../../mixins/conversations';
|
||||||
@ -59,7 +60,7 @@ export default {
|
|||||||
Thumbnail,
|
Thumbnail,
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [timeMixin, conversationMixin],
|
mixins: [timeMixin, conversationMixin, messageFormatterMixin],
|
||||||
props: {
|
props: {
|
||||||
activeLabel: {
|
activeLabel: {
|
||||||
type: String,
|
type: String,
|
||||||
@ -129,6 +130,10 @@ export default {
|
|||||||
const { message_type: messageType } = lastMessage;
|
const { message_type: messageType } = lastMessage;
|
||||||
return messageType === MESSAGE_TYPE.OUTGOING;
|
return messageType === MESSAGE_TYPE.OUTGOING;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
parsedLastMessage() {
|
||||||
|
return this.getPlainText(this.lastMessageInChat.content);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
|
|||||||
@ -1,15 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<li v-if="hasAttachments || data.content" :class="alignBubble">
|
<li v-if="hasAttachments || data.content" :class="alignBubble">
|
||||||
<div :class="wrapClass">
|
<div :class="wrapClass">
|
||||||
<p v-tooltip.top-start="sentByMessage" :class="bubbleClass">
|
<div v-tooltip.top-start="sentByMessage" :class="bubbleClass">
|
||||||
<bubble-text
|
<bubble-text
|
||||||
v-if="data.content"
|
v-if="data.content"
|
||||||
:message="message"
|
:message="message"
|
||||||
:is-email="isEmailContentType"
|
:is-email="isEmailContentType"
|
||||||
:readable-time="readableTime"
|
:readable-time="readableTime"
|
||||||
/>
|
/>
|
||||||
<span v-if="hasAttachments">
|
<span
|
||||||
<span v-for="attachment in data.attachments" :key="attachment.id">
|
v-if="isPending && hasAttachments"
|
||||||
|
class="chat-bubble has-attachment agent"
|
||||||
|
>
|
||||||
|
{{ $t('CONVERSATION.UPLOADING_ATTACHMENTS') }}
|
||||||
|
</span>
|
||||||
|
<div v-if="!isPending && hasAttachments">
|
||||||
|
<div v-for="attachment in data.attachments" :key="attachment.id">
|
||||||
<bubble-image
|
<bubble-image
|
||||||
v-if="attachment.file_type === 'image'"
|
v-if="attachment.file_type === 'image'"
|
||||||
:url="attachment.data_url"
|
:url="attachment.data_url"
|
||||||
@ -20,8 +26,9 @@
|
|||||||
:url="attachment.data_url"
|
:url="attachment.data_url"
|
||||||
:readable-time="readableTime"
|
:readable-time="readableTime"
|
||||||
/>
|
/>
|
||||||
</span>
|
</div>
|
||||||
</span>
|
</div>
|
||||||
|
|
||||||
<bubble-actions
|
<bubble-actions
|
||||||
:id="data.id"
|
:id="data.id"
|
||||||
:sender="data.sender"
|
:sender="data.sender"
|
||||||
@ -32,9 +39,16 @@
|
|||||||
:readable-time="readableTime"
|
:readable-time="readableTime"
|
||||||
:source-id="data.source_id"
|
:source-id="data.source_id"
|
||||||
/>
|
/>
|
||||||
</p>
|
</div>
|
||||||
|
<spinner v-if="isPending" size="tiny" />
|
||||||
|
|
||||||
<div v-if="isATweet && isIncoming && sender" class="sender--info">
|
<a
|
||||||
|
v-if="isATweet && isIncoming && sender"
|
||||||
|
class="sender--info"
|
||||||
|
:href="twitterProfileLink"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer nofollow"
|
||||||
|
>
|
||||||
<woot-thumbnail
|
<woot-thumbnail
|
||||||
:src="sender.thumbnail"
|
:src="sender.thumbnail"
|
||||||
:username="sender.name"
|
:username="sender.name"
|
||||||
@ -43,7 +57,7 @@
|
|||||||
<div class="sender--available-name">
|
<div class="sender--available-name">
|
||||||
{{ sender.name }}
|
{{ sender.name }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</template>
|
</template>
|
||||||
@ -53,9 +67,11 @@ import timeMixin from '../../../mixins/time';
|
|||||||
import BubbleText from './bubble/Text';
|
import BubbleText from './bubble/Text';
|
||||||
import BubbleImage from './bubble/Image';
|
import BubbleImage from './bubble/Image';
|
||||||
import BubbleFile from './bubble/File';
|
import BubbleFile from './bubble/File';
|
||||||
|
import Spinner from 'shared/components/Spinner';
|
||||||
|
|
||||||
import contentTypeMixin from 'shared/mixins/contentTypeMixin';
|
import contentTypeMixin from 'shared/mixins/contentTypeMixin';
|
||||||
import BubbleActions from './bubble/Actions';
|
import BubbleActions from './bubble/Actions';
|
||||||
import { MESSAGE_TYPE } from 'shared/constants/messageTypes';
|
import { MESSAGE_TYPE, MESSAGE_STATUS } from 'shared/constants/messages';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@ -63,6 +79,7 @@ export default {
|
|||||||
BubbleText,
|
BubbleText,
|
||||||
BubbleImage,
|
BubbleImage,
|
||||||
BubbleFile,
|
BubbleFile,
|
||||||
|
Spinner,
|
||||||
},
|
},
|
||||||
mixins: [timeMixin, messageFormatterMixin, contentTypeMixin],
|
mixins: [timeMixin, messageFormatterMixin, contentTypeMixin],
|
||||||
props: {
|
props: {
|
||||||
@ -93,6 +110,11 @@ export default {
|
|||||||
} = this;
|
} = this;
|
||||||
return contentType;
|
return contentType;
|
||||||
},
|
},
|
||||||
|
twitterProfileLink() {
|
||||||
|
const additionalAttributes = this.sender.additional_attributes || {};
|
||||||
|
const { screen_name: screenName } = additionalAttributes;
|
||||||
|
return `https://twitter.com/${screenName}`;
|
||||||
|
},
|
||||||
alignBubble() {
|
alignBubble() {
|
||||||
return !this.data.message_type ? 'left' : 'right';
|
return !this.data.message_type ? 'left' : 'right';
|
||||||
},
|
},
|
||||||
@ -116,6 +138,9 @@ export default {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
hasText() {
|
||||||
|
return !!this.data.content;
|
||||||
|
},
|
||||||
sentByMessage() {
|
sentByMessage() {
|
||||||
const { sender } = this;
|
const { sender } = this;
|
||||||
|
|
||||||
@ -130,6 +155,7 @@ export default {
|
|||||||
return {
|
return {
|
||||||
wrap: this.isBubble,
|
wrap: this.isBubble,
|
||||||
'activity-wrap': !this.isBubble,
|
'activity-wrap': !this.isBubble,
|
||||||
|
'is-pending': this.isPending,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
bubbleClass() {
|
bubbleClass() {
|
||||||
@ -137,25 +163,58 @@ export default {
|
|||||||
bubble: this.isBubble,
|
bubble: this.isBubble,
|
||||||
'is-private': this.data.private,
|
'is-private': this.data.private,
|
||||||
'is-image': this.hasImageAttachment,
|
'is-image': this.hasImageAttachment,
|
||||||
|
'is-text': this.hasText,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
isPending() {
|
||||||
|
return this.data.status === MESSAGE_STATUS.PROGRESS;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.wrap > .is-image.bubble {
|
.wrap {
|
||||||
padding: 0;
|
> .bubble {
|
||||||
overflow: hidden;
|
&.is-image {
|
||||||
|
padding: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
.image {
|
.image {
|
||||||
max-width: 32rem;
|
max-width: 32rem;
|
||||||
padding: 0;
|
padding: var(--space-micro);
|
||||||
|
|
||||||
|
> img {
|
||||||
|
border-radius: var(--border-radius-medium);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-image.is-text > .message-text__wrap {
|
||||||
|
max-width: 32rem;
|
||||||
|
padding: var(--space-small) var(--space-normal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-pending {
|
||||||
|
position: relative;
|
||||||
|
opacity: 0.8;
|
||||||
|
|
||||||
|
.spinner {
|
||||||
|
position: absolute;
|
||||||
|
bottom: var(--space-smaller);
|
||||||
|
right: var(--space-smaller);
|
||||||
|
}
|
||||||
|
|
||||||
|
> .is-image.is-text.bubble > .message-text__wrap {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sender--info {
|
.sender--info {
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
color: var(--b-700);
|
||||||
|
display: inline-flex;
|
||||||
padding: var(--space-smaller) 0;
|
padding: var(--space-smaller) 0;
|
||||||
|
|
||||||
.sender--available-name {
|
.sender--available-name {
|
||||||
|
|||||||
@ -1,6 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="reply-box" :class="replyBoxClass">
|
<div class="reply-box" :class="replyBoxClass">
|
||||||
<div class="reply-box__top" :class="{ 'is-private': isPrivate }">
|
<reply-top-panel
|
||||||
|
:mode="replyType"
|
||||||
|
:set-reply-mode="setReplyMode"
|
||||||
|
:is-message-length-reaching-threshold="isMessageLengthReachingThreshold"
|
||||||
|
:characters-remaining="charactersRemaining"
|
||||||
|
/>
|
||||||
|
<div class="reply-box__top">
|
||||||
<canned-response
|
<canned-response
|
||||||
v-if="showCannedResponsesList"
|
v-if="showCannedResponsesList"
|
||||||
v-on-clickaway="hideCannedResponse"
|
v-on-clickaway="hideCannedResponse"
|
||||||
@ -14,6 +20,7 @@
|
|||||||
:on-click="emojiOnClick"
|
:on-click="emojiOnClick"
|
||||||
/>
|
/>
|
||||||
<resizable-text-area
|
<resizable-text-area
|
||||||
|
v-if="!isFormatMode"
|
||||||
ref="messageInput"
|
ref="messageInput"
|
||||||
v-model="message"
|
v-model="message"
|
||||||
class="input"
|
class="input"
|
||||||
@ -24,71 +31,52 @@
|
|||||||
@focus="onFocus"
|
@focus="onFocus"
|
||||||
@blur="onBlur"
|
@blur="onBlur"
|
||||||
/>
|
/>
|
||||||
<file-upload
|
<woot-message-editor
|
||||||
v-if="showFileUpload"
|
v-else
|
||||||
:size="4096 * 4096"
|
v-model="message"
|
||||||
accept="image/*, application/pdf, audio/mpeg, video/mp4, audio/ogg, text/csv"
|
class="input"
|
||||||
@input-file="onFileUpload"
|
:placeholder="messagePlaceHolder"
|
||||||
>
|
:min-height="4"
|
||||||
<i v-if="!isUploading" class="icon ion-android-attach attachment" />
|
@typing-off="onTypingOff"
|
||||||
<woot-spinner v-if="isUploading" />
|
@typing-on="onTypingOn"
|
||||||
</file-upload>
|
@focus="onFocus"
|
||||||
<i
|
@blur="onBlur"
|
||||||
class="icon ion-happy-outline"
|
|
||||||
:class="{ active: showEmojiPicker }"
|
|
||||||
@click="toggleEmojiPicker"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div v-if="hasAttachments" class="attachment-preview-box">
|
||||||
<div class="reply-box__bottom">
|
<attachment-preview
|
||||||
<ul class="tabs">
|
:attachments="attachedFiles"
|
||||||
<li class="tabs-title" :class="{ 'is-active': !isPrivate }">
|
:remove-attachment="removeAttachment"
|
||||||
<a href="#" @click="setReplyMode">{{
|
/>
|
||||||
$t('CONVERSATION.REPLYBOX.REPLY')
|
|
||||||
}}</a>
|
|
||||||
</li>
|
|
||||||
<li class="tabs-title is-private" :class="{ 'is-active': isPrivate }">
|
|
||||||
<a href="#" @click="setPrivateReplyMode">
|
|
||||||
{{ $t('CONVERSATION.REPLYBOX.PRIVATE_NOTE') }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li v-if="message.length" class="tabs-title message-length">
|
|
||||||
<a :class="{ 'message-error': isMessageLengthReachingThreshold }">
|
|
||||||
{{ characterCountIndicator }}
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
class="button send-button"
|
|
||||||
:disabled="isReplyButtonDisabled"
|
|
||||||
:class="{
|
|
||||||
disabled: isReplyButtonDisabled,
|
|
||||||
warning: isPrivate,
|
|
||||||
}"
|
|
||||||
@click="sendMessage"
|
|
||||||
>
|
|
||||||
{{ replyButtonLabel }}
|
|
||||||
<i
|
|
||||||
class="icon"
|
|
||||||
:class="{
|
|
||||||
'ion-android-send': !isPrivate,
|
|
||||||
'ion-android-lock': isPrivate,
|
|
||||||
}"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
<reply-bottom-panel
|
||||||
|
:mode="replyType"
|
||||||
|
:send-button-text="replyButtonLabel"
|
||||||
|
:on-file-upload="onFileUpload"
|
||||||
|
:show-file-upload="showFileUpload"
|
||||||
|
:toggle-emoji-picker="toggleEmojiPicker"
|
||||||
|
:show-emoji-picker="showEmojiPicker"
|
||||||
|
:on-send="sendMessage"
|
||||||
|
:is-send-disabled="isReplyButtonDisabled"
|
||||||
|
:set-format-mode="setFormatMode"
|
||||||
|
:is-format-mode="isFormatMode"
|
||||||
|
:enable-rich-editor="isRichEditorEnabled"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import { mixin as clickaway } from 'vue-clickaway';
|
import { mixin as clickaway } from 'vue-clickaway';
|
||||||
import FileUpload from 'vue-upload-component';
|
|
||||||
|
|
||||||
import EmojiInput from 'shared/components/emoji/EmojiInput';
|
import EmojiInput from 'shared/components/emoji/EmojiInput';
|
||||||
import CannedResponse from './CannedResponse';
|
import CannedResponse from './CannedResponse';
|
||||||
import ResizableTextArea from 'shared/components/ResizableTextArea';
|
import ResizableTextArea from 'shared/components/ResizableTextArea';
|
||||||
|
import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview';
|
||||||
|
import ReplyTopPanel from 'dashboard/components/widgets/WootWriter/ReplyTopPanel';
|
||||||
|
import ReplyBottomPanel from 'dashboard/components/widgets/WootWriter/ReplyBottomPanel';
|
||||||
|
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
|
||||||
|
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor';
|
||||||
import {
|
import {
|
||||||
isEscape,
|
isEscape,
|
||||||
isEnter,
|
isEnter,
|
||||||
@ -101,8 +89,11 @@ export default {
|
|||||||
components: {
|
components: {
|
||||||
EmojiInput,
|
EmojiInput,
|
||||||
CannedResponse,
|
CannedResponse,
|
||||||
FileUpload,
|
|
||||||
ResizableTextArea,
|
ResizableTextArea,
|
||||||
|
AttachmentPreview,
|
||||||
|
ReplyTopPanel,
|
||||||
|
ReplyBottomPanel,
|
||||||
|
WootMessageEditor,
|
||||||
},
|
},
|
||||||
mixins: [clickaway, inboxMixin],
|
mixins: [clickaway, inboxMixin],
|
||||||
props: {
|
props: {
|
||||||
@ -114,18 +105,20 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
message: '',
|
message: '',
|
||||||
isPrivateTabActive: false,
|
|
||||||
isFocused: false,
|
isFocused: false,
|
||||||
showEmojiPicker: false,
|
showEmojiPicker: false,
|
||||||
showCannedResponsesList: false,
|
showCannedResponsesList: false,
|
||||||
|
attachedFiles: [],
|
||||||
isUploading: false,
|
isUploading: false,
|
||||||
|
replyType: REPLY_EDITOR_MODES.REPLY,
|
||||||
|
isFormatMode: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({ currentChat: 'getSelectedChat' }),
|
...mapGetters({ currentChat: 'getSelectedChat' }),
|
||||||
isPrivate() {
|
isPrivate() {
|
||||||
if (this.currentChat.can_reply) {
|
if (this.currentChat.can_reply) {
|
||||||
return this.isPrivateTabActive;
|
return this.replyType === REPLY_EDITOR_MODES.NOTE;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
@ -141,13 +134,15 @@ export default {
|
|||||||
: this.$t('CONVERSATION.FOOTER.MSG_INPUT');
|
: this.$t('CONVERSATION.FOOTER.MSG_INPUT');
|
||||||
},
|
},
|
||||||
isMessageLengthReachingThreshold() {
|
isMessageLengthReachingThreshold() {
|
||||||
return this.message.length > this.maxLength - 40;
|
return this.message.length > this.maxLength - 50;
|
||||||
},
|
},
|
||||||
characterCountIndicator() {
|
charactersRemaining() {
|
||||||
return `${this.message.length} / ${this.maxLength}`;
|
return this.maxLength - this.message.length;
|
||||||
},
|
},
|
||||||
isReplyButtonDisabled() {
|
isReplyButtonDisabled() {
|
||||||
const isMessageEmpty = !this.message.trim().replace(/\n/g, '').length;
|
const isMessageEmpty = !this.message.trim().replace(/\n/g, '').length;
|
||||||
|
|
||||||
|
if (this.hasAttachments) return false;
|
||||||
return (
|
return (
|
||||||
isMessageEmpty ||
|
isMessageEmpty ||
|
||||||
this.message.length === 0 ||
|
this.message.length === 0 ||
|
||||||
@ -196,16 +191,28 @@ export default {
|
|||||||
},
|
},
|
||||||
replyBoxClass() {
|
replyBoxClass() {
|
||||||
return {
|
return {
|
||||||
'is-focused': this.isFocused,
|
'is-private': this.isPrivate,
|
||||||
|
'is-focused': this.isFocused || this.hasAttachments,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
hasAttachments() {
|
||||||
|
return this.attachedFiles.length;
|
||||||
|
},
|
||||||
|
isRichEditorEnabled() {
|
||||||
|
return (
|
||||||
|
this.isAWebWidgetInbox ||
|
||||||
|
this.isAnEmailChannel ||
|
||||||
|
this.replyType === REPLY_EDITOR_MODES.NOTE
|
||||||
|
);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
currentChat(conversation) {
|
currentChat(conversation) {
|
||||||
if (conversation.can_reply) {
|
const { can_reply: canReply } = conversation;
|
||||||
this.isPrivateTabActive = false;
|
if (canReply) {
|
||||||
|
this.replyType = REPLY_EDITOR_MODES.REPLY;
|
||||||
} else {
|
} else {
|
||||||
this.isPrivateTabActive = true;
|
this.replyType = REPLY_EDITOR_MODES.NOTE;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
message(updatedMessage) {
|
message(updatedMessage) {
|
||||||
@ -240,7 +247,9 @@ export default {
|
|||||||
this.hideEmojiPicker();
|
this.hideEmojiPicker();
|
||||||
this.hideCannedResponse();
|
this.hideCannedResponse();
|
||||||
} else if (isEnter(e)) {
|
} else if (isEnter(e)) {
|
||||||
if (!hasPressedShift(e)) {
|
const shouldSendMessage =
|
||||||
|
!this.isFormatMode && !hasPressedShift(e) && this.isFocused;
|
||||||
|
if (shouldSendMessage) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
this.sendMessage();
|
this.sendMessage();
|
||||||
}
|
}
|
||||||
@ -250,18 +259,11 @@ export default {
|
|||||||
if (this.isReplyButtonDisabled) {
|
if (this.isReplyButtonDisabled) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const newMessage = this.message;
|
|
||||||
if (!this.showCannedResponsesList) {
|
if (!this.showCannedResponsesList) {
|
||||||
|
const newMessage = this.message;
|
||||||
|
const messagePayload = this.getMessagePayload(newMessage);
|
||||||
this.clearMessage();
|
this.clearMessage();
|
||||||
try {
|
try {
|
||||||
const messagePayload = {
|
|
||||||
conversationId: this.currentChat.id,
|
|
||||||
message: newMessage,
|
|
||||||
private: this.isPrivate,
|
|
||||||
};
|
|
||||||
if (this.inReplyTo) {
|
|
||||||
messagePayload.contentAttributes = { in_reply_to: this.inReplyTo };
|
|
||||||
}
|
|
||||||
await this.$store.dispatch('sendMessage', messagePayload);
|
await this.$store.dispatch('sendMessage', messagePayload);
|
||||||
this.$emit('scrollToMessage');
|
this.$emit('scrollToMessage');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -275,12 +277,10 @@ export default {
|
|||||||
this.message = message;
|
this.message = message;
|
||||||
}, 100);
|
}, 100);
|
||||||
},
|
},
|
||||||
setPrivateReplyMode() {
|
setReplyMode(mode = REPLY_EDITOR_MODES.REPLY) {
|
||||||
this.isPrivateTabActive = true;
|
const { can_reply: canReply } = this.currentChat;
|
||||||
this.$refs.messageInput.focus();
|
|
||||||
},
|
if (canReply) this.replyType = mode;
|
||||||
setReplyMode() {
|
|
||||||
this.isPrivateTabActive = false;
|
|
||||||
this.$refs.messageInput.focus();
|
this.$refs.messageInput.focus();
|
||||||
},
|
},
|
||||||
emojiOnClick(emoji) {
|
emojiOnClick(emoji) {
|
||||||
@ -288,6 +288,7 @@ export default {
|
|||||||
},
|
},
|
||||||
clearMessage() {
|
clearMessage() {
|
||||||
this.message = '';
|
this.message = '';
|
||||||
|
this.attachedFiles = [];
|
||||||
},
|
},
|
||||||
toggleEmojiPicker() {
|
toggleEmojiPicker() {
|
||||||
this.showEmojiPicker = !this.showEmojiPicker;
|
this.showEmojiPicker = !this.showEmojiPicker;
|
||||||
@ -322,30 +323,91 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
onFileUpload(file) {
|
onFileUpload(file) {
|
||||||
|
this.attachedFiles = [];
|
||||||
if (!file) {
|
if (!file) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.isUploading = true;
|
const reader = new FileReader();
|
||||||
this.$store
|
reader.readAsDataURL(file.file);
|
||||||
.dispatch('sendAttachment', [
|
|
||||||
this.currentChat.id,
|
reader.onloadend = () => {
|
||||||
{ file: file.file, isPrivate: this.isPrivate },
|
this.attachedFiles.push({
|
||||||
])
|
currentChatId: this.currentChat.id,
|
||||||
.then(() => {
|
resource: file,
|
||||||
this.isUploading = false;
|
isPrivate: this.isPrivate,
|
||||||
this.$emit('scrollToMessage');
|
thumb: reader.result,
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
this.isUploading = false;
|
|
||||||
this.$emit('scrollToMessage');
|
|
||||||
});
|
});
|
||||||
|
};
|
||||||
|
},
|
||||||
|
removeAttachment(itemIndex) {
|
||||||
|
this.attachedFiles = this.attachedFiles.filter(
|
||||||
|
(item, index) => itemIndex !== index
|
||||||
|
);
|
||||||
|
},
|
||||||
|
getMessagePayload(message) {
|
||||||
|
const [attachment] = this.attachedFiles;
|
||||||
|
const messagePayload = {
|
||||||
|
conversationId: this.currentChat.id,
|
||||||
|
message,
|
||||||
|
private: this.isPrivate,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (this.inReplyTo) {
|
||||||
|
messagePayload.contentAttributes = { in_reply_to: this.inReplyTo };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachment) {
|
||||||
|
messagePayload.file = attachment.resource.file;
|
||||||
|
}
|
||||||
|
|
||||||
|
return messagePayload;
|
||||||
|
},
|
||||||
|
setFormatMode(value) {
|
||||||
|
this.isFormatMode = value;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss" scoped>
|
||||||
.send-button {
|
.send-button {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.attachment-preview-box {
|
||||||
|
padding: 0 var(--space-normal);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-box {
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
background: white;
|
||||||
|
|
||||||
|
&.is-private {
|
||||||
|
background: var(--y-50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.send-button {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reply-box__top {
|
||||||
|
padding: 0 var(--space-normal);
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
margin-top: -1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-dialog {
|
||||||
|
top: unset;
|
||||||
|
bottom: 12px;
|
||||||
|
left: -320px;
|
||||||
|
right: unset;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
right: -16px;
|
||||||
|
bottom: 10px;
|
||||||
|
transform: rotate(270deg);
|
||||||
|
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.08));
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -19,9 +19,13 @@
|
|||||||
class="icon ion-reply cursor-pointer"
|
class="icon ion-reply cursor-pointer"
|
||||||
@click="onTweetReply"
|
@click="onTweetReply"
|
||||||
/>
|
/>
|
||||||
<a :href="linkToTweet" target="_blank" rel="noopener noreferrer nofollow">
|
<a
|
||||||
|
v-if="isATweet && isIncoming"
|
||||||
|
:href="linkToTweet"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer nofollow"
|
||||||
|
>
|
||||||
<i
|
<i
|
||||||
v-if="isATweet && isIncoming"
|
|
||||||
v-tooltip.top-start="$t('CHAT_LIST.VIEW_TWEET_IN_TWITTER')"
|
v-tooltip.top-start="$t('CHAT_LIST.VIEW_TWEET_IN_TWITTER')"
|
||||||
class="icon ion-android-open cursor-pointer"
|
class="icon ion-android-open cursor-pointer"
|
||||||
/>
|
/>
|
||||||
@ -30,7 +34,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { MESSAGE_TYPE } from 'shared/constants/messageTypes';
|
import { MESSAGE_TYPE } from 'shared/constants/messages';
|
||||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<span class="message-text__wrap">
|
<div class="message-text__wrap">
|
||||||
<span v-html="message"></span>
|
<div class="text-content" v-html="message"></div>
|
||||||
</span>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
@ -6,8 +6,12 @@
|
|||||||
:type="type"
|
:type="type"
|
||||||
:placeholder="placeholder"
|
:placeholder="placeholder"
|
||||||
@input="onChange"
|
@input="onChange"
|
||||||
|
@blur="onBlur"
|
||||||
/>
|
/>
|
||||||
<p v-if="helpText" class="help-text"></p>
|
<p v-if="helpText" class="help-text"></p>
|
||||||
|
<span v-if="error" class="message">
|
||||||
|
{{ error }}
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@ -34,11 +38,18 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
error: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
onChange(e) {
|
onChange(e) {
|
||||||
this.$emit('input', e.target.value);
|
this.$emit('input', e.target.value);
|
||||||
},
|
},
|
||||||
|
onBlur(e) {
|
||||||
|
this.$emit('blur', e.target.value);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
/* eslint no-console: 0 */
|
|
||||||
/* eslint no-param-reassign: 0 */
|
/* eslint no-param-reassign: 0 */
|
||||||
|
|
||||||
|
import getUuid from 'widget/helpers/uuid';
|
||||||
|
import { MESSAGE_STATUS, MESSAGE_TYPE } from 'shared/constants/messages';
|
||||||
|
|
||||||
export default () => {
|
export default () => {
|
||||||
if (!Array.prototype.last) {
|
if (!Array.prototype.last) {
|
||||||
Object.assign(Array.prototype, {
|
Object.assign(Array.prototype, {
|
||||||
@ -26,3 +29,23 @@ export const getTypingUsersText = (users = []) => {
|
|||||||
const rest = users.length - 1;
|
const rest = users.length - 1;
|
||||||
return `${user.name} and ${rest} others are typing`;
|
return `${user.name} and ${rest} others are typing`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const createPendingMessage = data => {
|
||||||
|
const timestamp = Math.floor(new Date().getTime() / 1000);
|
||||||
|
const tempMessageId = getUuid();
|
||||||
|
const { message, file } = data;
|
||||||
|
const tempAttachments = [{ id: tempMessageId }];
|
||||||
|
const pendingMessage = {
|
||||||
|
...data,
|
||||||
|
content: message || null,
|
||||||
|
id: tempMessageId,
|
||||||
|
echo_id: tempMessageId,
|
||||||
|
status: MESSAGE_STATUS.PROGRESS,
|
||||||
|
created_at: timestamp,
|
||||||
|
message_type: MESSAGE_TYPE.OUTGOING,
|
||||||
|
conversation_id: data.conversationId,
|
||||||
|
attachments: file ? tempAttachments : null,
|
||||||
|
};
|
||||||
|
|
||||||
|
return pendingMessage;
|
||||||
|
};
|
||||||
|
|||||||
11
app/javascript/dashboard/helper/files.js
Normal file
11
app/javascript/dashboard/helper/files.js
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
export const formatBytes = (bytes, decimals = 2) => {
|
||||||
|
if (bytes === 0) return '0 Bytes';
|
||||||
|
|
||||||
|
const k = 1024;
|
||||||
|
const dm = decimals < 0 ? 0 : decimals;
|
||||||
|
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||||
|
|
||||||
|
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||||
|
|
||||||
|
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||||
|
};
|
||||||
@ -1,4 +1,4 @@
|
|||||||
import { getTypingUsersText } from '../commons';
|
import { getTypingUsersText, createPendingMessage } from '../commons';
|
||||||
|
|
||||||
describe('#getTypingUsersText', () => {
|
describe('#getTypingUsersText', () => {
|
||||||
it('returns the correct text is there is only one typing user', () => {
|
it('returns the correct text is there is only one typing user', () => {
|
||||||
@ -24,3 +24,62 @@ describe('#getTypingUsersText', () => {
|
|||||||
).toEqual('Pranav and 3 others are typing');
|
).toEqual('Pranav and 3 others are typing');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#createPendingMessage', () => {
|
||||||
|
const message = {
|
||||||
|
message: 'hi',
|
||||||
|
};
|
||||||
|
it('returns the pending message with expected new keys', () => {
|
||||||
|
expect(createPendingMessage(message)).toHaveProperty(
|
||||||
|
'content',
|
||||||
|
'id',
|
||||||
|
'status',
|
||||||
|
'echo_id',
|
||||||
|
'status',
|
||||||
|
'created_at',
|
||||||
|
'message_type',
|
||||||
|
'conversation_id'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the pending message with status progress', () => {
|
||||||
|
expect(createPendingMessage(message)).toMatchObject({
|
||||||
|
status: 'progress',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the pending message with same id and echo_id', () => {
|
||||||
|
const pending = createPendingMessage(message);
|
||||||
|
expect(pending).toMatchObject({
|
||||||
|
echo_id: pending.id,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the pending message with attachmnet key if file is passed', () => {
|
||||||
|
const messageWithFile = {
|
||||||
|
message: 'hi',
|
||||||
|
file: {},
|
||||||
|
};
|
||||||
|
expect(createPendingMessage(messageWithFile)).toHaveProperty(
|
||||||
|
'content',
|
||||||
|
'id',
|
||||||
|
'status',
|
||||||
|
'echo_id',
|
||||||
|
'status',
|
||||||
|
'created_at',
|
||||||
|
'message_type',
|
||||||
|
'conversation_id',
|
||||||
|
'attachments',
|
||||||
|
'private'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the pending message to have one attachment', () => {
|
||||||
|
const messageWithFile = {
|
||||||
|
message: 'hi',
|
||||||
|
file: {},
|
||||||
|
};
|
||||||
|
const pending = createPendingMessage(messageWithFile);
|
||||||
|
expect(pending.attachments.length).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
18
app/javascript/dashboard/helper/specs/files.spec.js
Normal file
18
app/javascript/dashboard/helper/specs/files.spec.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { formatBytes } from '../files';
|
||||||
|
|
||||||
|
describe('#File Helpers', () => {
|
||||||
|
describe('formatBytes', () => {
|
||||||
|
it('should return zero bytes if 0 is passed', () => {
|
||||||
|
expect(formatBytes(0)).toBe('0 Bytes');
|
||||||
|
});
|
||||||
|
it('should return in bytes if 1000 is passed', () => {
|
||||||
|
expect(formatBytes(1000)).toBe('1000 Bytes');
|
||||||
|
});
|
||||||
|
it('should return in KB if 100000 is passed', () => {
|
||||||
|
expect(formatBytes(10000)).toBe('9.77 KB');
|
||||||
|
});
|
||||||
|
it('should return in MB if 10000000 is passed', () => {
|
||||||
|
expect(formatBytes(10000000)).toBe('9.54 MB');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@ -25,7 +25,7 @@ export const getSidebarItems = accountId => ({
|
|||||||
toStateName: 'home',
|
toStateName: 'home',
|
||||||
},
|
},
|
||||||
contacts: {
|
contacts: {
|
||||||
icon: 'ion-person-stalker',
|
icon: 'ion-person',
|
||||||
label: 'CONTACTS',
|
label: 'CONTACTS',
|
||||||
hasSubMenu: false,
|
hasSubMenu: false,
|
||||||
toState: frontendURL(`accounts/${accountId}/contacts`),
|
toState: frontendURL(`accounts/${accountId}/contacts`),
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
"OS": "نظام التشغيل",
|
"OS": "نظام التشغيل",
|
||||||
"INITIATED_FROM": "تم البدء من",
|
"INITIATED_FROM": "تم البدء من",
|
||||||
"INITIATED_AT": "تم البدء في",
|
"INITIATED_AT": "تم البدء في",
|
||||||
|
"IP_ADDRESS": "IP Address",
|
||||||
"CONVERSATIONS": {
|
"CONVERSATIONS": {
|
||||||
"NO_RECORDS_FOUND": "لا توجد محادثات سابقة مرتبطة بجهة الاتصال هذه.",
|
"NO_RECORDS_FOUND": "لا توجد محادثات سابقة مرتبطة بجهة الاتصال هذه.",
|
||||||
"TITLE": "المحادثات السابقة"
|
"TITLE": "المحادثات السابقة"
|
||||||
|
|||||||
@ -24,6 +24,7 @@
|
|||||||
"REPLYING_TO": "أنت ترد على:",
|
"REPLYING_TO": "أنت ترد على:",
|
||||||
"REMOVE_SELECTION": "إزالة التحديد",
|
"REMOVE_SELECTION": "إزالة التحديد",
|
||||||
"DOWNLOAD": "تنزيل",
|
"DOWNLOAD": "تنزيل",
|
||||||
|
"UPLOADING_ATTACHMENTS": "Uploading attachments...",
|
||||||
"HEADER": {
|
"HEADER": {
|
||||||
"RESOLVE_ACTION": "إغلاق المحادثة",
|
"RESOLVE_ACTION": "إغلاق المحادثة",
|
||||||
"REOPEN_ACTION": "إعادة فتح",
|
"REOPEN_ACTION": "إعادة فتح",
|
||||||
@ -40,7 +41,10 @@
|
|||||||
"PRIVATE_NOTE": "إضافة ملاحظة خاصة",
|
"PRIVATE_NOTE": "إضافة ملاحظة خاصة",
|
||||||
"SEND": "إرسال",
|
"SEND": "إرسال",
|
||||||
"CREATE": "إضافة ملاحظة",
|
"CREATE": "إضافة ملاحظة",
|
||||||
"TWEET": "تغريد"
|
"TWEET": "تغريد",
|
||||||
|
"TIP_FORMAT_ICON": "Show rich text editor",
|
||||||
|
"TIP_EMOJI_ICON": "Show emoji selector",
|
||||||
|
"TIP_ATTACH_ICON": "Attach files"
|
||||||
},
|
},
|
||||||
"VISIBLE_TO_AGENTS": "ملاحظة خاصة: مرئية فقط لأعضاء فريق العمل والموظفين",
|
"VISIBLE_TO_AGENTS": "ملاحظة خاصة: مرئية فقط لأعضاء فريق العمل والموظفين",
|
||||||
"CHANGE_STATUS": "تم تغيير حالة المحادثة",
|
"CHANGE_STATUS": "تم تغيير حالة المحادثة",
|
||||||
|
|||||||
@ -42,7 +42,8 @@
|
|||||||
"INBOUND_EMAIL_ENABLED": "الاستمرار في المحادثة عبر رسائل البريد الإلكتروني مفعّل لحسابك.",
|
"INBOUND_EMAIL_ENABLED": "الاستمرار في المحادثة عبر رسائل البريد الإلكتروني مفعّل لحسابك.",
|
||||||
"CUSTOM_EMAIL_DOMAIN_ENABLED": "يمكنك تلقي رسائل البريد الإلكتروني في النطاق المخصص الخاص بك الآن."
|
"CUSTOM_EMAIL_DOMAIN_ENABLED": "يمكنك تلقي رسائل البريد الإلكتروني في النطاق المخصص الخاص بك الآن."
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"UPDATE_CHATWOOT": "An update %{latestChatwootVersion} for Chatwoot is available. Please update your instance."
|
||||||
},
|
},
|
||||||
"FORMS": {
|
"FORMS": {
|
||||||
"MULTISELECT": {
|
"MULTISELECT": {
|
||||||
|
|||||||
@ -241,7 +241,9 @@
|
|||||||
"AUTO_ASSIGNMENT": "تفعيل الإسناد التلقائي",
|
"AUTO_ASSIGNMENT": "تفعيل الإسناد التلقائي",
|
||||||
"INBOX_UPDATE_TITLE": "إعدادات قناة التواصل",
|
"INBOX_UPDATE_TITLE": "إعدادات قناة التواصل",
|
||||||
"INBOX_UPDATE_SUB_TEXT": "تحديث إعدادات قناة التواصل",
|
"INBOX_UPDATE_SUB_TEXT": "تحديث إعدادات قناة التواصل",
|
||||||
"AUTO_ASSIGNMENT_SUB_TEXT": "تمكين أو تعطيل الإسناد التلقائي للمحادثات الجديدة إلى الموظفين المضافين إلى قناة التواصل هذه."
|
"AUTO_ASSIGNMENT_SUB_TEXT": "تمكين أو تعطيل الإسناد التلقائي للمحادثات الجديدة إلى الموظفين المضافين إلى قناة التواصل هذه.",
|
||||||
|
"HMAC_VERIFICATION": "User Identity Validation",
|
||||||
|
"HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here."
|
||||||
},
|
},
|
||||||
"FACEBOOK_REAUTHORIZE": {
|
"FACEBOOK_REAUTHORIZE": {
|
||||||
"TITLE": "إعادة التصريح",
|
"TITLE": "إعادة التصريح",
|
||||||
|
|||||||
@ -113,7 +113,7 @@
|
|||||||
"SIDEBAR": {
|
"SIDEBAR": {
|
||||||
"CONVERSATIONS": "المحادثات",
|
"CONVERSATIONS": "المحادثات",
|
||||||
"REPORTS": "التقارير",
|
"REPORTS": "التقارير",
|
||||||
"CONTACTS": "Contacts (Beta)",
|
"CONTACTS": "Contacts",
|
||||||
"SETTINGS": "الإعدادات",
|
"SETTINGS": "الإعدادات",
|
||||||
"HOME": "الرئيسية",
|
"HOME": "الرئيسية",
|
||||||
"AGENTS": "موظف الدعم",
|
"AGENTS": "موظف الدعم",
|
||||||
|
|||||||
@ -5,13 +5,18 @@
|
|||||||
"TERMS_ACCEPT": "من خلال التسجيل، فإنك توافق على <a href=\"https://www.chatwoot.com/terms\">شروط الخدمة</a> و <a href=\"https://www.chatwoot.com/privacy-policy\">سياسة الخصوصية</a>",
|
"TERMS_ACCEPT": "من خلال التسجيل، فإنك توافق على <a href=\"https://www.chatwoot.com/terms\">شروط الخدمة</a> و <a href=\"https://www.chatwoot.com/privacy-policy\">سياسة الخصوصية</a>",
|
||||||
"ACCOUNT_NAME": {
|
"ACCOUNT_NAME": {
|
||||||
"LABEL": "اسم الحساب",
|
"LABEL": "اسم الحساب",
|
||||||
"PLACEHOLDER": "مؤسسة Wayne",
|
"PLACEHOLDER": "Enter an account name. eg: Wayne Enterprises",
|
||||||
"ERROR": "اسم الحساب قصير جداً"
|
"ERROR": "Account name is too short"
|
||||||
|
},
|
||||||
|
"FULL_NAME": {
|
||||||
|
"LABEL": "Full name",
|
||||||
|
"PLACEHOLDER": "Enter your full name. eg: Bruce Wayne",
|
||||||
|
"ERROR": "Full name is too short"
|
||||||
},
|
},
|
||||||
"EMAIL": {
|
"EMAIL": {
|
||||||
"LABEL": "البريد الإلكتروني",
|
"LABEL": "Work email",
|
||||||
"PLACEHOLDER": "bruce@wayne.enterprises",
|
"PLACEHOLDER": "Enter your work email address. eg: bruce@wayne.enterprises",
|
||||||
"ERROR": "البريد الإلكتروني غير صالح"
|
"ERROR": "Email address is invalid"
|
||||||
},
|
},
|
||||||
"PASSWORD": {
|
"PASSWORD": {
|
||||||
"LABEL": "كلمة المرور",
|
"LABEL": "كلمة المرور",
|
||||||
@ -28,12 +33,6 @@
|
|||||||
"ERROR_MESSAGE": "تعذر الاتصال بالخادم، الرجاء المحاولة مرة أخرى لاحقاً"
|
"ERROR_MESSAGE": "تعذر الاتصال بالخادم، الرجاء المحاولة مرة أخرى لاحقاً"
|
||||||
},
|
},
|
||||||
"SUBMIT": "إرسال",
|
"SUBMIT": "إرسال",
|
||||||
"FEATURES": {
|
"HAVE_AN_ACCOUNT": "Already have an account?"
|
||||||
"UNLIMITED_INBOXES": "Unlimited inboxes",
|
|
||||||
"ROBUST_REPORTING": "Robust Reporting",
|
|
||||||
"CANNED_RESPONSES": "الردود السريعة",
|
|
||||||
"AUTO_ASSIGNMENT": "Auto Assignment",
|
|
||||||
"SECURITY": "Enterprise level security"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
"OS": "Sistema operatiu",
|
"OS": "Sistema operatiu",
|
||||||
"INITIATED_FROM": "Iniciada des de",
|
"INITIATED_FROM": "Iniciada des de",
|
||||||
"INITIATED_AT": "Iniciada a les",
|
"INITIATED_AT": "Iniciada a les",
|
||||||
|
"IP_ADDRESS": "Adreça IP",
|
||||||
"CONVERSATIONS": {
|
"CONVERSATIONS": {
|
||||||
"NO_RECORDS_FOUND": "No hi han converses prèvies associades a aquest contacte.",
|
"NO_RECORDS_FOUND": "No hi han converses prèvies associades a aquest contacte.",
|
||||||
"TITLE": "Converses prèvies"
|
"TITLE": "Converses prèvies"
|
||||||
|
|||||||
@ -13,8 +13,8 @@
|
|||||||
"PLACEHOLDER": "Escriu qualsevol text per cercar missatges",
|
"PLACEHOLDER": "Escriu qualsevol text per cercar missatges",
|
||||||
"NO_MATCHING_RESULTS": "No hi ha missatges que coincideixin amb els paràmetres de cerca."
|
"NO_MATCHING_RESULTS": "No hi ha missatges que coincideixin amb els paràmetres de cerca."
|
||||||
},
|
},
|
||||||
"UNREAD_MESSAGES": "Unread Messages",
|
"UNREAD_MESSAGES": "Missatges no Llegits",
|
||||||
"UNREAD_MESSAGE": "Unread Message",
|
"UNREAD_MESSAGE": "Missatge no Llegit",
|
||||||
"CLICK_HERE": "Clica aquí",
|
"CLICK_HERE": "Clica aquí",
|
||||||
"LOADING_INBOXES": "S'estan carregant les safates d'entrada",
|
"LOADING_INBOXES": "S'estan carregant les safates d'entrada",
|
||||||
"LOADING_CONVERSATIONS": "S'estan carregant les converses",
|
"LOADING_CONVERSATIONS": "S'estan carregant les converses",
|
||||||
@ -24,6 +24,7 @@
|
|||||||
"REPLYING_TO": "Estas responent a:",
|
"REPLYING_TO": "Estas responent a:",
|
||||||
"REMOVE_SELECTION": "Elimina la selecció",
|
"REMOVE_SELECTION": "Elimina la selecció",
|
||||||
"DOWNLOAD": "Descarrega",
|
"DOWNLOAD": "Descarrega",
|
||||||
|
"UPLOADING_ATTACHMENTS": "Pujant fitxers adjunts...",
|
||||||
"HEADER": {
|
"HEADER": {
|
||||||
"RESOLVE_ACTION": "Resoldre",
|
"RESOLVE_ACTION": "Resoldre",
|
||||||
"REOPEN_ACTION": "Tornar a obrir",
|
"REOPEN_ACTION": "Tornar a obrir",
|
||||||
@ -40,7 +41,10 @@
|
|||||||
"PRIVATE_NOTE": "Nota privada",
|
"PRIVATE_NOTE": "Nota privada",
|
||||||
"SEND": "Envia",
|
"SEND": "Envia",
|
||||||
"CREATE": "Afegeix una nota",
|
"CREATE": "Afegeix una nota",
|
||||||
"TWEET": "Tuit"
|
"TWEET": "Tuit",
|
||||||
|
"TIP_FORMAT_ICON": "Show rich text editor",
|
||||||
|
"TIP_EMOJI_ICON": "Show emoji selector",
|
||||||
|
"TIP_ATTACH_ICON": "Attach files"
|
||||||
},
|
},
|
||||||
"VISIBLE_TO_AGENTS": "Nota privada: Només és visible per tu i el vostre equip",
|
"VISIBLE_TO_AGENTS": "Nota privada: Només és visible per tu i el vostre equip",
|
||||||
"CHANGE_STATUS": "Estat de la conversa canviat",
|
"CHANGE_STATUS": "Estat de la conversa canviat",
|
||||||
|
|||||||
@ -42,7 +42,8 @@
|
|||||||
"INBOUND_EMAIL_ENABLED": "La continuïtat de converses amb correus electrònics està habilitada per al vostre compte.",
|
"INBOUND_EMAIL_ENABLED": "La continuïtat de converses amb correus electrònics està habilitada per al vostre compte.",
|
||||||
"CUSTOM_EMAIL_DOMAIN_ENABLED": "Ara podeu rebre correus electrònics al vostre domini personalitzat."
|
"CUSTOM_EMAIL_DOMAIN_ENABLED": "Ara podeu rebre correus electrònics al vostre domini personalitzat."
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"UPDATE_CHATWOOT": "An update %{latestChatwootVersion} for Chatwoot is available. Please update your instance."
|
||||||
},
|
},
|
||||||
"FORMS": {
|
"FORMS": {
|
||||||
"MULTISELECT": {
|
"MULTISELECT": {
|
||||||
|
|||||||
@ -241,7 +241,9 @@
|
|||||||
"AUTO_ASSIGNMENT": "Activa l'assignació automàtica",
|
"AUTO_ASSIGNMENT": "Activa l'assignació automàtica",
|
||||||
"INBOX_UPDATE_TITLE": "Configuració de la safata d'entrada",
|
"INBOX_UPDATE_TITLE": "Configuració de la safata d'entrada",
|
||||||
"INBOX_UPDATE_SUB_TEXT": "Actualitza la configuració de la safata d'entrada",
|
"INBOX_UPDATE_SUB_TEXT": "Actualitza la configuració de la safata d'entrada",
|
||||||
"AUTO_ASSIGNMENT_SUB_TEXT": "Activa o desactiva l'assignació automàtica d'agents disponibles a les noves converses"
|
"AUTO_ASSIGNMENT_SUB_TEXT": "Activa o desactiva l'assignació automàtica d'agents disponibles a les noves converses",
|
||||||
|
"HMAC_VERIFICATION": "User Identity Validation",
|
||||||
|
"HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here."
|
||||||
},
|
},
|
||||||
"FACEBOOK_REAUTHORIZE": {
|
"FACEBOOK_REAUTHORIZE": {
|
||||||
"TITLE": "Reautoritza",
|
"TITLE": "Reautoritza",
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable */
|
|
||||||
import { default as _agentMgmt } from './agentMgmt.json';
|
import { default as _agentMgmt } from './agentMgmt.json';
|
||||||
|
import { default as _labelsMgmt } from './labelsMgmt.json';
|
||||||
import { default as _cannedMgmt } from './cannedMgmt.json';
|
import { default as _cannedMgmt } from './cannedMgmt.json';
|
||||||
import { default as _chatlist } from './chatlist.json';
|
import { default as _chatlist } from './chatlist.json';
|
||||||
import { default as _contact } from './contact.json';
|
import { default as _contact } from './contact.json';
|
||||||
@ -23,6 +23,7 @@ export default {
|
|||||||
..._inboxMgmt,
|
..._inboxMgmt,
|
||||||
..._login,
|
..._login,
|
||||||
..._report,
|
..._report,
|
||||||
|
..._labelsMgmt,
|
||||||
..._resetPassword,
|
..._resetPassword,
|
||||||
..._setNewPassword,
|
..._setNewPassword,
|
||||||
..._settings,
|
..._settings,
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"HEADER": "Informes",
|
"HEADER": "Informes",
|
||||||
"LOADING_CHART": "S'estan carregant dades del gràfic...",
|
"LOADING_CHART": "S'estan carregant dades del gràfic...",
|
||||||
"NO_ENOUGH_DATA": "No hem rebut suficients punts de dades per generar l'informe. Torneu-ho a provar més endavant.",
|
"NO_ENOUGH_DATA": "No hem rebut suficients punts de dades per generar l'informe. Torneu-ho a provar més endavant.",
|
||||||
"DOWNLOAD_AGENT_REPORTS": "Download agent reports",
|
"DOWNLOAD_AGENT_REPORTS": "Descarregar Informes d'Agent",
|
||||||
"METRICS": {
|
"METRICS": {
|
||||||
"CONVERSATIONS": {
|
"CONVERSATIONS": {
|
||||||
"NAME": "Converses",
|
"NAME": "Converses",
|
||||||
|
|||||||
@ -113,7 +113,7 @@
|
|||||||
"SIDEBAR": {
|
"SIDEBAR": {
|
||||||
"CONVERSATIONS": "Converses",
|
"CONVERSATIONS": "Converses",
|
||||||
"REPORTS": "Informes",
|
"REPORTS": "Informes",
|
||||||
"CONTACTS": "Contactes (Beta)",
|
"CONTACTS": "Contactes",
|
||||||
"SETTINGS": "Configuracions",
|
"SETTINGS": "Configuracions",
|
||||||
"HOME": "Inici",
|
"HOME": "Inici",
|
||||||
"AGENTS": "Agents",
|
"AGENTS": "Agents",
|
||||||
|
|||||||
@ -5,13 +5,18 @@
|
|||||||
"TERMS_ACCEPT": "En registrar-vos, esteu d’acord amb el nostre <a href=\"https://www.chatwoot.com/terms\">T & C</a> i <a href=\"https://www.chatwoot.com/privacy-policy\">Polítiques de Privadesa</a>",
|
"TERMS_ACCEPT": "En registrar-vos, esteu d’acord amb el nostre <a href=\"https://www.chatwoot.com/terms\">T & C</a> i <a href=\"https://www.chatwoot.com/privacy-policy\">Polítiques de Privadesa</a>",
|
||||||
"ACCOUNT_NAME": {
|
"ACCOUNT_NAME": {
|
||||||
"LABEL": "Nom del compte",
|
"LABEL": "Nom del compte",
|
||||||
"PLACEHOLDER": "Wayne Enterprises",
|
"PLACEHOLDER": "Introdueix el nom del compte. ex: Wayne Enterprises",
|
||||||
|
"ERROR": "El nom del compte és massa curt"
|
||||||
|
},
|
||||||
|
"FULL_NAME": {
|
||||||
|
"LABEL": "Nom complet",
|
||||||
|
"PLACEHOLDER": "Introdueix el teu nom complet. ex: Bruce Wayne",
|
||||||
"ERROR": "El nom del compte és massa curt"
|
"ERROR": "El nom del compte és massa curt"
|
||||||
},
|
},
|
||||||
"EMAIL": {
|
"EMAIL": {
|
||||||
"LABEL": "Correu electrònic",
|
"LABEL": "Email de treball",
|
||||||
"PLACEHOLDER": "bruce@wayne.enterprises",
|
"PLACEHOLDER": "Introdueix la teva adreça email de treball. ex: bruce@wayne.enterprises",
|
||||||
"ERROR": "El correu electrònic no és correcte"
|
"ERROR": "Adreça email invàlida"
|
||||||
},
|
},
|
||||||
"PASSWORD": {
|
"PASSWORD": {
|
||||||
"LABEL": "Contrasenya",
|
"LABEL": "Contrasenya",
|
||||||
@ -28,12 +33,6 @@
|
|||||||
"ERROR_MESSAGE": "No s'ha pogut connectar amb el servidor Woot. Torna-ho a provar més endavant"
|
"ERROR_MESSAGE": "No s'ha pogut connectar amb el servidor Woot. Torna-ho a provar més endavant"
|
||||||
},
|
},
|
||||||
"SUBMIT": "Envia",
|
"SUBMIT": "Envia",
|
||||||
"FEATURES": {
|
"HAVE_AN_ACCOUNT": "Ja tens un compte?"
|
||||||
"UNLIMITED_INBOXES": "Safates ilimitades",
|
|
||||||
"ROBUST_REPORTING": "Informa robust",
|
|
||||||
"CANNED_RESPONSES": "Respostes predeterminades",
|
|
||||||
"AUTO_ASSIGNMENT": "Tasca Automàtica",
|
|
||||||
"SECURITY": "Seguretat a nivell empreserial"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
"OS": "Operační systém",
|
"OS": "Operační systém",
|
||||||
"INITIATED_FROM": "Zahájeno od",
|
"INITIATED_FROM": "Zahájeno od",
|
||||||
"INITIATED_AT": "Zahájeno v",
|
"INITIATED_AT": "Zahájeno v",
|
||||||
|
"IP_ADDRESS": "IP adresa",
|
||||||
"CONVERSATIONS": {
|
"CONVERSATIONS": {
|
||||||
"NO_RECORDS_FOUND": "K tomuto kontaktu nejsou přiřazeny žádné předchozí konverzace.",
|
"NO_RECORDS_FOUND": "K tomuto kontaktu nejsou přiřazeny žádné předchozí konverzace.",
|
||||||
"TITLE": "Předchozí konverzace"
|
"TITLE": "Předchozí konverzace"
|
||||||
|
|||||||
@ -13,8 +13,8 @@
|
|||||||
"PLACEHOLDER": "Zadejte jakýkoli text k hledání",
|
"PLACEHOLDER": "Zadejte jakýkoli text k hledání",
|
||||||
"NO_MATCHING_RESULTS": "Vašemu vyhledávání neodpovídají žádné zprávy."
|
"NO_MATCHING_RESULTS": "Vašemu vyhledávání neodpovídají žádné zprávy."
|
||||||
},
|
},
|
||||||
"UNREAD_MESSAGES": "Unread Messages",
|
"UNREAD_MESSAGES": "Nepřečtené zprávy",
|
||||||
"UNREAD_MESSAGE": "Unread Message",
|
"UNREAD_MESSAGE": "Nepřečtená zpráva",
|
||||||
"CLICK_HERE": "Klikněte zde",
|
"CLICK_HERE": "Klikněte zde",
|
||||||
"LOADING_INBOXES": "Načítání krabic",
|
"LOADING_INBOXES": "Načítání krabic",
|
||||||
"LOADING_CONVERSATIONS": "Načítání konverzací",
|
"LOADING_CONVERSATIONS": "Načítání konverzací",
|
||||||
@ -24,6 +24,7 @@
|
|||||||
"REPLYING_TO": "Odpovídáte uživateli:",
|
"REPLYING_TO": "Odpovídáte uživateli:",
|
||||||
"REMOVE_SELECTION": "Odstranit výběr",
|
"REMOVE_SELECTION": "Odstranit výběr",
|
||||||
"DOWNLOAD": "Stáhnout",
|
"DOWNLOAD": "Stáhnout",
|
||||||
|
"UPLOADING_ATTACHMENTS": "Nahrávání příloh...",
|
||||||
"HEADER": {
|
"HEADER": {
|
||||||
"RESOLVE_ACTION": "Vyřešit",
|
"RESOLVE_ACTION": "Vyřešit",
|
||||||
"REOPEN_ACTION": "Znovu otevřít",
|
"REOPEN_ACTION": "Znovu otevřít",
|
||||||
@ -40,16 +41,19 @@
|
|||||||
"PRIVATE_NOTE": "Soukromá poznámka",
|
"PRIVATE_NOTE": "Soukromá poznámka",
|
||||||
"SEND": "Poslat",
|
"SEND": "Poslat",
|
||||||
"CREATE": "Přidat poznámku",
|
"CREATE": "Přidat poznámku",
|
||||||
"TWEET": "Tweet"
|
"TWEET": "Tweet",
|
||||||
|
"TIP_FORMAT_ICON": "Show rich text editor",
|
||||||
|
"TIP_EMOJI_ICON": "Show emoji selector",
|
||||||
|
"TIP_ATTACH_ICON": "Attach files"
|
||||||
},
|
},
|
||||||
"VISIBLE_TO_AGENTS": "Soukromá poznámka: Viditelné pouze pro vás a váš tým",
|
"VISIBLE_TO_AGENTS": "Soukromá poznámka: Viditelné pouze pro vás a váš tým",
|
||||||
"CHANGE_STATUS": "Stav konverzace byl změněn",
|
"CHANGE_STATUS": "Stav konverzace byl změněn",
|
||||||
"CHANGE_AGENT": "Konverzace pověřená osoba změněna",
|
"CHANGE_AGENT": "Konverzace pověřená osoba změněna",
|
||||||
"SENT_BY": "Sent by:",
|
"SENT_BY": "Odeslal:",
|
||||||
"ASSIGNMENT": {
|
"ASSIGNMENT": {
|
||||||
"SELECT_AGENT": "Select Agent",
|
"SELECT_AGENT": "Vybrat agenta",
|
||||||
"REMOVE": "Odebrat",
|
"REMOVE": "Odebrat",
|
||||||
"ASSIGN": "Assign"
|
"ASSIGN": "Přiřadit"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"EMAIL_TRANSCRIPT": {
|
"EMAIL_TRANSCRIPT": {
|
||||||
|
|||||||
@ -42,13 +42,14 @@
|
|||||||
"INBOUND_EMAIL_ENABLED": "E-mailová konverzace je u vašeho účtu povolena.",
|
"INBOUND_EMAIL_ENABLED": "E-mailová konverzace je u vašeho účtu povolena.",
|
||||||
"CUSTOM_EMAIL_DOMAIN_ENABLED": "Nyní můžete přijímat e-maily na vaši vlastní doménu."
|
"CUSTOM_EMAIL_DOMAIN_ENABLED": "Nyní můžete přijímat e-maily na vaši vlastní doménu."
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"UPDATE_CHATWOOT": "An update %{latestChatwootVersion} for Chatwoot is available. Please update your instance."
|
||||||
},
|
},
|
||||||
"FORMS": {
|
"FORMS": {
|
||||||
"MULTISELECT": {
|
"MULTISELECT": {
|
||||||
"ENTER_TO_SELECT": "Press enter to select",
|
"ENTER_TO_SELECT": "Stiskněte Enter pro vybrání",
|
||||||
"ENTER_TO_REMOVE": "Press enter to remove",
|
"ENTER_TO_REMOVE": "Stiskněte Enter pro odebrání",
|
||||||
"SELECT_ONE": "Select one"
|
"SELECT_ONE": "Vyberte jeden"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -75,10 +75,10 @@
|
|||||||
},
|
},
|
||||||
"REPLY_TIME": {
|
"REPLY_TIME": {
|
||||||
"TITLE": "Nastavit čas odpovědi",
|
"TITLE": "Nastavit čas odpovědi",
|
||||||
"IN_A_FEW_MINUTES": "In a few minutes",
|
"IN_A_FEW_MINUTES": "Do několika minut",
|
||||||
"IN_A_FEW_HOURS": "In a few hours",
|
"IN_A_FEW_HOURS": "Do několika hodin",
|
||||||
"IN_A_DAY": "In a day",
|
"IN_A_DAY": "Do dne",
|
||||||
"HELP_TEXT": "This reply time will be displayed on the live chat widget"
|
"HELP_TEXT": "Tento čas odpovědi bude zobrazen na widgetu"
|
||||||
},
|
},
|
||||||
"WIDGET_COLOR": {
|
"WIDGET_COLOR": {
|
||||||
"LABEL": "Barva widgetu",
|
"LABEL": "Barva widgetu",
|
||||||
@ -241,7 +241,9 @@
|
|||||||
"AUTO_ASSIGNMENT": "Povolit automatické přiřazení",
|
"AUTO_ASSIGNMENT": "Povolit automatické přiřazení",
|
||||||
"INBOX_UPDATE_TITLE": "Nastavení doručené pošty",
|
"INBOX_UPDATE_TITLE": "Nastavení doručené pošty",
|
||||||
"INBOX_UPDATE_SUB_TEXT": "Aktualizujte nastavení doručené pošty",
|
"INBOX_UPDATE_SUB_TEXT": "Aktualizujte nastavení doručené pošty",
|
||||||
"AUTO_ASSIGNMENT_SUB_TEXT": "Povolit nebo zakázat automatické přiřazování nových konverzací agentům přidaným do této schránky."
|
"AUTO_ASSIGNMENT_SUB_TEXT": "Povolit nebo zakázat automatické přiřazování nových konverzací agentům přidaným do této schránky.",
|
||||||
|
"HMAC_VERIFICATION": "User Identity Validation",
|
||||||
|
"HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here."
|
||||||
},
|
},
|
||||||
"FACEBOOK_REAUTHORIZE": {
|
"FACEBOOK_REAUTHORIZE": {
|
||||||
"TITLE": "Znovu autorizovat",
|
"TITLE": "Znovu autorizovat",
|
||||||
|
|||||||
@ -113,7 +113,7 @@
|
|||||||
"SIDEBAR": {
|
"SIDEBAR": {
|
||||||
"CONVERSATIONS": "Konverzace",
|
"CONVERSATIONS": "Konverzace",
|
||||||
"REPORTS": "Zprávy",
|
"REPORTS": "Zprávy",
|
||||||
"CONTACTS": "Contacts (Beta)",
|
"CONTACTS": "Kontakty",
|
||||||
"SETTINGS": "Nastavení",
|
"SETTINGS": "Nastavení",
|
||||||
"HOME": "Home",
|
"HOME": "Home",
|
||||||
"AGENTS": "Agenti",
|
"AGENTS": "Agenti",
|
||||||
|
|||||||
@ -5,13 +5,18 @@
|
|||||||
"TERMS_ACCEPT": "Registrací souhlasíte s našimi <a href=\"https://www.chatwoot.com/terms\">T & C</a> a <a href=\"https://www.chatwoot.com/privacy-policy\">Zásadami ochrany osobních údajů</a>",
|
"TERMS_ACCEPT": "Registrací souhlasíte s našimi <a href=\"https://www.chatwoot.com/terms\">T & C</a> a <a href=\"https://www.chatwoot.com/privacy-policy\">Zásadami ochrany osobních údajů</a>",
|
||||||
"ACCOUNT_NAME": {
|
"ACCOUNT_NAME": {
|
||||||
"LABEL": "Název účtu",
|
"LABEL": "Název účtu",
|
||||||
"PLACEHOLDER": "Wayne podniky",
|
"PLACEHOLDER": "Enter an account name. eg: Wayne Enterprises",
|
||||||
"ERROR": "Název účtu je příliš krátký"
|
"ERROR": "Account name is too short"
|
||||||
|
},
|
||||||
|
"FULL_NAME": {
|
||||||
|
"LABEL": "Full name",
|
||||||
|
"PLACEHOLDER": "Enter your full name. eg: Bruce Wayne",
|
||||||
|
"ERROR": "Full name is too short"
|
||||||
},
|
},
|
||||||
"EMAIL": {
|
"EMAIL": {
|
||||||
"LABEL": "E-mailová adresa",
|
"LABEL": "Work email",
|
||||||
"PLACEHOLDER": "bruce@wayne.enterprises",
|
"PLACEHOLDER": "Enter your work email address. eg: bruce@wayne.enterprises",
|
||||||
"ERROR": "E-mail je neplatný"
|
"ERROR": "Email address is invalid"
|
||||||
},
|
},
|
||||||
"PASSWORD": {
|
"PASSWORD": {
|
||||||
"LABEL": "Heslo",
|
"LABEL": "Heslo",
|
||||||
@ -28,12 +33,6 @@
|
|||||||
"ERROR_MESSAGE": "Nelze se připojit k Woot serveru, opakujte akci později"
|
"ERROR_MESSAGE": "Nelze se připojit k Woot serveru, opakujte akci později"
|
||||||
},
|
},
|
||||||
"SUBMIT": "Odeslat",
|
"SUBMIT": "Odeslat",
|
||||||
"FEATURES": {
|
"HAVE_AN_ACCOUNT": "Already have an account?"
|
||||||
"UNLIMITED_INBOXES": "Unlimited inboxes",
|
|
||||||
"ROBUST_REPORTING": "Robust Reporting",
|
|
||||||
"CANNED_RESPONSES": "Konzervované odpovědi",
|
|
||||||
"AUTO_ASSIGNMENT": "Auto Assignment",
|
|
||||||
"SECURITY": "Enterprise level security"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,6 +11,7 @@
|
|||||||
"OS": "Operativsystem",
|
"OS": "Operativsystem",
|
||||||
"INITIATED_FROM": "Startet fra",
|
"INITIATED_FROM": "Startet fra",
|
||||||
"INITIATED_AT": "Startet fra",
|
"INITIATED_AT": "Startet fra",
|
||||||
|
"IP_ADDRESS": "IP Address",
|
||||||
"CONVERSATIONS": {
|
"CONVERSATIONS": {
|
||||||
"NO_RECORDS_FOUND": "Der er ingen tidligere samtaler tilknyttet denne kontakt.",
|
"NO_RECORDS_FOUND": "Der er ingen tidligere samtaler tilknyttet denne kontakt.",
|
||||||
"TITLE": "Tidligere Samtaler"
|
"TITLE": "Tidligere Samtaler"
|
||||||
|
|||||||
@ -24,6 +24,7 @@
|
|||||||
"REPLYING_TO": "Du svarer til:",
|
"REPLYING_TO": "Du svarer til:",
|
||||||
"REMOVE_SELECTION": "Fjern Markering",
|
"REMOVE_SELECTION": "Fjern Markering",
|
||||||
"DOWNLOAD": "Download",
|
"DOWNLOAD": "Download",
|
||||||
|
"UPLOADING_ATTACHMENTS": "Uploading attachments...",
|
||||||
"HEADER": {
|
"HEADER": {
|
||||||
"RESOLVE_ACTION": "Løs",
|
"RESOLVE_ACTION": "Løs",
|
||||||
"REOPEN_ACTION": "Genåben",
|
"REOPEN_ACTION": "Genåben",
|
||||||
@ -40,7 +41,10 @@
|
|||||||
"PRIVATE_NOTE": "Privat Note",
|
"PRIVATE_NOTE": "Privat Note",
|
||||||
"SEND": "Send",
|
"SEND": "Send",
|
||||||
"CREATE": "Tilføj Note",
|
"CREATE": "Tilføj Note",
|
||||||
"TWEET": "Tweet"
|
"TWEET": "Tweet",
|
||||||
|
"TIP_FORMAT_ICON": "Show rich text editor",
|
||||||
|
"TIP_EMOJI_ICON": "Show emoji selector",
|
||||||
|
"TIP_ATTACH_ICON": "Attach files"
|
||||||
},
|
},
|
||||||
"VISIBLE_TO_AGENTS": "Privat Note: Kun synlig for dig og dit team",
|
"VISIBLE_TO_AGENTS": "Privat Note: Kun synlig for dig og dit team",
|
||||||
"CHANGE_STATUS": "Samtalestatus ændret",
|
"CHANGE_STATUS": "Samtalestatus ændret",
|
||||||
|
|||||||
@ -42,7 +42,8 @@
|
|||||||
"INBOUND_EMAIL_ENABLED": "Samtale kontinuitet med e-mails er aktiveret for din konto.",
|
"INBOUND_EMAIL_ENABLED": "Samtale kontinuitet med e-mails er aktiveret for din konto.",
|
||||||
"CUSTOM_EMAIL_DOMAIN_ENABLED": "Du kan modtage e-mails på dit brugerdefinerede domæne nu."
|
"CUSTOM_EMAIL_DOMAIN_ENABLED": "Du kan modtage e-mails på dit brugerdefinerede domæne nu."
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"UPDATE_CHATWOOT": "An update %{latestChatwootVersion} for Chatwoot is available. Please update your instance."
|
||||||
},
|
},
|
||||||
"FORMS": {
|
"FORMS": {
|
||||||
"MULTISELECT": {
|
"MULTISELECT": {
|
||||||
|
|||||||
@ -241,7 +241,9 @@
|
|||||||
"AUTO_ASSIGNMENT": "Aktiver automatisk tildeling",
|
"AUTO_ASSIGNMENT": "Aktiver automatisk tildeling",
|
||||||
"INBOX_UPDATE_TITLE": "Indbakke Indstillinger",
|
"INBOX_UPDATE_TITLE": "Indbakke Indstillinger",
|
||||||
"INBOX_UPDATE_SUB_TEXT": "Opdater dine indbakkeindstillinger",
|
"INBOX_UPDATE_SUB_TEXT": "Opdater dine indbakkeindstillinger",
|
||||||
"AUTO_ASSIGNMENT_SUB_TEXT": "Aktiver eller deaktiver automatisk tildeling af nye samtaler til agenter tilføjet til denne indbakke."
|
"AUTO_ASSIGNMENT_SUB_TEXT": "Aktiver eller deaktiver automatisk tildeling af nye samtaler til agenter tilføjet til denne indbakke.",
|
||||||
|
"HMAC_VERIFICATION": "User Identity Validation",
|
||||||
|
"HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here."
|
||||||
},
|
},
|
||||||
"FACEBOOK_REAUTHORIZE": {
|
"FACEBOOK_REAUTHORIZE": {
|
||||||
"TITLE": "Genautorisér",
|
"TITLE": "Genautorisér",
|
||||||
|
|||||||
@ -113,7 +113,7 @@
|
|||||||
"SIDEBAR": {
|
"SIDEBAR": {
|
||||||
"CONVERSATIONS": "Samtaler",
|
"CONVERSATIONS": "Samtaler",
|
||||||
"REPORTS": "Rapporter",
|
"REPORTS": "Rapporter",
|
||||||
"CONTACTS": "Kontakter (Beta)",
|
"CONTACTS": "Kontakter",
|
||||||
"SETTINGS": "Indstillinger",
|
"SETTINGS": "Indstillinger",
|
||||||
"HOME": "Hjem",
|
"HOME": "Hjem",
|
||||||
"AGENTS": "Agenter",
|
"AGENTS": "Agenter",
|
||||||
|
|||||||
@ -5,13 +5,18 @@
|
|||||||
"TERMS_ACCEPT": "Ved at tilmelde dig, accepterer du vores <a href=\"https://www.chatwoot.com/terms\">T & C</a> og <a href=\"https://www.chatwoot.com/privacy-policy\">Privatlivspolitik</a>",
|
"TERMS_ACCEPT": "Ved at tilmelde dig, accepterer du vores <a href=\"https://www.chatwoot.com/terms\">T & C</a> og <a href=\"https://www.chatwoot.com/privacy-policy\">Privatlivspolitik</a>",
|
||||||
"ACCOUNT_NAME": {
|
"ACCOUNT_NAME": {
|
||||||
"LABEL": "Kontonavn",
|
"LABEL": "Kontonavn",
|
||||||
"PLACEHOLDER": "Wayne Enterprises",
|
"PLACEHOLDER": "Enter an account name. eg: Wayne Enterprises",
|
||||||
"ERROR": "Kontonavn er for kort"
|
"ERROR": "Account name is too short"
|
||||||
|
},
|
||||||
|
"FULL_NAME": {
|
||||||
|
"LABEL": "Full name",
|
||||||
|
"PLACEHOLDER": "Enter your full name. eg: Bruce Wayne",
|
||||||
|
"ERROR": "Full name is too short"
|
||||||
},
|
},
|
||||||
"EMAIL": {
|
"EMAIL": {
|
||||||
"LABEL": "E-mail",
|
"LABEL": "Work email",
|
||||||
"PLACEHOLDER": "bruce@wayne.enterprises",
|
"PLACEHOLDER": "Enter your work email address. eg: bruce@wayne.enterprises",
|
||||||
"ERROR": "E-mail er ugyldig"
|
"ERROR": "Email address is invalid"
|
||||||
},
|
},
|
||||||
"PASSWORD": {
|
"PASSWORD": {
|
||||||
"LABEL": "Adgangskode",
|
"LABEL": "Adgangskode",
|
||||||
@ -28,12 +33,6 @@
|
|||||||
"ERROR_MESSAGE": "Kunne ikke oprette forbindelse til Woot Server, Prøv igen senere"
|
"ERROR_MESSAGE": "Kunne ikke oprette forbindelse til Woot Server, Prøv igen senere"
|
||||||
},
|
},
|
||||||
"SUBMIT": "Send",
|
"SUBMIT": "Send",
|
||||||
"FEATURES": {
|
"HAVE_AN_ACCOUNT": "Already have an account?"
|
||||||
"UNLIMITED_INBOXES": "Unlimited inboxes",
|
|
||||||
"ROBUST_REPORTING": "Robust Reporting",
|
|
||||||
"CANNED_RESPONSES": "Standardsvar Svar",
|
|
||||||
"AUTO_ASSIGNMENT": "Auto Assignment",
|
|
||||||
"SECURITY": "Enterprise level security"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -77,8 +77,8 @@
|
|||||||
"CONTENT": "hat eine URL geteilt"
|
"CONTENT": "hat eine URL geteilt"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"RECEIVED_VIA_EMAIL": "Received via email",
|
"RECEIVED_VIA_EMAIL": "Per E-Mail empfangen",
|
||||||
"VIEW_TWEET_IN_TWITTER": "View tweet in Twitter",
|
"VIEW_TWEET_IN_TWITTER": "Tweet auf Twitter anzeigen",
|
||||||
"REPLY_TO_TWEET": "Reply to this tweet"
|
"REPLY_TO_TWEET": "Auf diesen Tweet antworten"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,113 +1,114 @@
|
|||||||
{
|
{
|
||||||
"CONTACT_PANEL": {
|
"CONTACT_PANEL": {
|
||||||
"NOT_AVAILABLE": "Not Available",
|
"NOT_AVAILABLE": "Nicht verfügbar",
|
||||||
"EMAIL_ADDRESS": "E-Mail-Addresse",
|
"EMAIL_ADDRESS": "E-Mail-Addresse",
|
||||||
"PHONE_NUMBER": "Telefonnummer",
|
"PHONE_NUMBER": "Telefonnummer",
|
||||||
"COPY_SUCCESSFUL": "Copied to clipboard successfully",
|
"COPY_SUCCESSFUL": "Der Code wurde erfolgreich in die Zwischenablage kopiert",
|
||||||
"COMPANY": "Company",
|
"COMPANY": "Firma",
|
||||||
"LOCATION": "Ort",
|
"LOCATION": "Ort",
|
||||||
"CONVERSATION_TITLE": "Unterhaltungsdetails",
|
"CONVERSATION_TITLE": "Unterhaltungsdetails",
|
||||||
"BROWSER": "Browser",
|
"BROWSER": "Browser",
|
||||||
"OS": "Betriebssystem",
|
"OS": "Betriebssystem",
|
||||||
"INITIATED_FROM": "Initiiert von",
|
"INITIATED_FROM": "Initiiert von",
|
||||||
"INITIATED_AT": "Initiiert bei",
|
"INITIATED_AT": "Initiiert bei",
|
||||||
|
"IP_ADDRESS": "IP-Adresse",
|
||||||
"CONVERSATIONS": {
|
"CONVERSATIONS": {
|
||||||
"NO_RECORDS_FOUND": "Es sind keine vorherigen Gespräche mit diesem Kontakt verbunden.",
|
"NO_RECORDS_FOUND": "Es sind keine vorherigen Gespräche mit diesem Kontakt verbunden.",
|
||||||
"TITLE": "Vorherige Gespräche"
|
"TITLE": "Vorherige Gespräche"
|
||||||
},
|
},
|
||||||
"CUSTOM_ATTRIBUTES": {
|
"CUSTOM_ATTRIBUTES": {
|
||||||
"TITLE": "Custom Attributes"
|
"TITLE": "Benutzerdefinierte Attribute"
|
||||||
},
|
},
|
||||||
"LABELS": {
|
"LABELS": {
|
||||||
"TITLE": "Konversationsetiketten",
|
"TITLE": "Konversationsetiketten",
|
||||||
"MODAL": {
|
"MODAL": {
|
||||||
"TITLE": "Labels for",
|
"TITLE": "Labels für",
|
||||||
"ACTIVE_LABELS": "Labels added to the conversation",
|
"ACTIVE_LABELS": "Labels zur Unterhaltung hinzugefügt",
|
||||||
"INACTIVE_LABELS": "Labels available in the account",
|
"INACTIVE_LABELS": "Verfügbare Labels im Konto",
|
||||||
"REMOVE": "Click on X icon to remove the label",
|
"REMOVE": "Klicken Sie auf das X-Symbol, um das Label zu entfernen",
|
||||||
"ADD": "Click on + icon to add the label",
|
"ADD": "Klicken Sie auf das + Symbol, um ein Label hinzuzufügen",
|
||||||
"UPDATE_BUTTON": "Update labels",
|
"UPDATE_BUTTON": "Labels aktualisieren",
|
||||||
"UPDATE_ERROR": "Etiketten konnten nicht aktualisiert werden. Versuchen Sie es erneut."
|
"UPDATE_ERROR": "Etiketten konnten nicht aktualisiert werden. Versuchen Sie es erneut."
|
||||||
},
|
},
|
||||||
"NO_LABELS_TO_ADD": "There are no more labels defined in the account.",
|
"NO_LABELS_TO_ADD": "Es sind keine weiteren Labels im Konto definiert.",
|
||||||
"NO_AVAILABLE_LABELS": "There are no labels added to this conversation."
|
"NO_AVAILABLE_LABELS": "Zu dieser Unterhaltung wurden noch keine Labels hinzugefügt."
|
||||||
},
|
},
|
||||||
"MUTE_CONTACT": "Mute Conversation",
|
"MUTE_CONTACT": "Unterhaltung stummschalten",
|
||||||
"UNMUTE_CONTACT": "Unmute Conversation",
|
"UNMUTE_CONTACT": "Unterhaltung entmuten",
|
||||||
"MUTED_SUCCESS": "This conversation is muted for 6 hours",
|
"MUTED_SUCCESS": "Diese Unterhaltung ist für 6 Stunden auf stumm schalten",
|
||||||
"UNMUTED_SUCCESS": "This conversation is unmuted",
|
"UNMUTED_SUCCESS": "Diese Unterhaltung ist nicht mehr stumm geschaltet",
|
||||||
"SEND_TRANSCRIPT": "Send Transcript",
|
"SEND_TRANSCRIPT": "Transkript senden",
|
||||||
"EDIT_LABEL": "Bearbeiten"
|
"EDIT_LABEL": "Bearbeiten"
|
||||||
},
|
},
|
||||||
"EDIT_CONTACT": {
|
"EDIT_CONTACT": {
|
||||||
"BUTTON_LABEL": "Edit Contact",
|
"BUTTON_LABEL": "Kontakt bearbeiten",
|
||||||
"TITLE": "Edit contact",
|
"TITLE": "Kontakt bearbeiten",
|
||||||
"DESC": "Edit contact details",
|
"DESC": "Kontaktdetails bearbeiten",
|
||||||
"FORM": {
|
"FORM": {
|
||||||
"SUBMIT": "Einreichen",
|
"SUBMIT": "Einreichen",
|
||||||
"CANCEL": "Stornieren",
|
"CANCEL": "Stornieren",
|
||||||
"AVATAR": {
|
"AVATAR": {
|
||||||
"LABEL": "Contact Avatar"
|
"LABEL": "Kontaktbild"
|
||||||
},
|
},
|
||||||
"NAME": {
|
"NAME": {
|
||||||
"PLACEHOLDER": "Enter the full name of the contact",
|
"PLACEHOLDER": "Vollständigen Namen des Kontakts eingeben",
|
||||||
"LABEL": "Full Name"
|
"LABEL": "Vollständiger Name"
|
||||||
},
|
},
|
||||||
"BIO": {
|
"BIO": {
|
||||||
"PLACEHOLDER": "Enter the bio of the contact",
|
"PLACEHOLDER": "Beschreibung dieses Kontakts",
|
||||||
"LABEL": "Bio"
|
"LABEL": "Beschreibung"
|
||||||
},
|
},
|
||||||
"EMAIL_ADDRESS": {
|
"EMAIL_ADDRESS": {
|
||||||
"PLACEHOLDER": "Enter the email address of the contact",
|
"PLACEHOLDER": "Geben Sie die E-Mail-Adresse des Kontakts ein",
|
||||||
"LABEL": "E-Mail-Addresse"
|
"LABEL": "E-Mail-Addresse"
|
||||||
},
|
},
|
||||||
"PHONE_NUMBER": {
|
"PHONE_NUMBER": {
|
||||||
"PLACEHOLDER": "Enter the phone number of the contact",
|
"PLACEHOLDER": "Geben Sie die Telefonnummer des Kontakts ein",
|
||||||
"LABEL": "Phone Number"
|
"LABEL": "Telefonnummer"
|
||||||
},
|
},
|
||||||
"LOCATION": {
|
"LOCATION": {
|
||||||
"PLACEHOLDER": "Enter the location of the contact",
|
"PLACEHOLDER": "Geben Sie den Standort des Kontakts ein",
|
||||||
"LABEL": "Ort"
|
"LABEL": "Ort"
|
||||||
},
|
},
|
||||||
"COMPANY_NAME": {
|
"COMPANY_NAME": {
|
||||||
"PLACEHOLDER": "Enter the company name",
|
"PLACEHOLDER": "Firmenname eingeben",
|
||||||
"LABEL": "Company Name"
|
"LABEL": "Firmenname"
|
||||||
},
|
},
|
||||||
"SOCIAL_PROFILES": {
|
"SOCIAL_PROFILES": {
|
||||||
"FACEBOOK": {
|
"FACEBOOK": {
|
||||||
"PLACEHOLDER": "Enter the Facebook username",
|
"PLACEHOLDER": "Facebook-Benutzername eingeben",
|
||||||
"LABEL": "Facebook"
|
"LABEL": "Facebook"
|
||||||
},
|
},
|
||||||
"TWITTER": {
|
"TWITTER": {
|
||||||
"PLACEHOLDER": "Enter the Twitter username",
|
"PLACEHOLDER": "Twitter-Benutzername eingeben",
|
||||||
"LABEL": "Twitter"
|
"LABEL": "Twitter"
|
||||||
},
|
},
|
||||||
"LINKEDIN": {
|
"LINKEDIN": {
|
||||||
"PLACEHOLDER": "Enter the LinkedIn username",
|
"PLACEHOLDER": "Geben Sie den LinkedIn-Benutzernamen ein",
|
||||||
"LABEL": "LinkedIn"
|
"LABEL": "LinkedIn"
|
||||||
},
|
},
|
||||||
"GITHUB": {
|
"GITHUB": {
|
||||||
"PLACEHOLDER": "Enter the Github username",
|
"PLACEHOLDER": "Gitub-Benutzernamen eingeben",
|
||||||
"LABEL": "Github"
|
"LABEL": "Github"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"SUCCESS_MESSAGE": "Updated contact successfully",
|
"SUCCESS_MESSAGE": "Kontakt erfolgreich aktualisiert",
|
||||||
"CONTACT_ALREADY_EXIST": "This email address is in use for another contact.",
|
"CONTACT_ALREADY_EXIST": "Diese E-Mail-Adresse wird bereits für einen anderen Kontakt verwendet.",
|
||||||
"ERROR_MESSAGE": "There was an error updating the contact, please try again"
|
"ERROR_MESSAGE": "Beim Aktualisieren des Kontakts ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut"
|
||||||
},
|
},
|
||||||
"CONTACTS_PAGE": {
|
"CONTACTS_PAGE": {
|
||||||
"HEADER": "Contacts",
|
"HEADER": "Kontakte",
|
||||||
"SEARCH_BUTTON": "Search",
|
"SEARCH_BUTTON": "Suchen",
|
||||||
"SEARCH_INPUT_PLACEHOLDER": "Search for contacts",
|
"SEARCH_INPUT_PLACEHOLDER": "Suche nach Kontakten",
|
||||||
"LIST": {
|
"LIST": {
|
||||||
"LOADING_MESSAGE": "Loading contacts...",
|
"LOADING_MESSAGE": "Kontakte werden geladen...",
|
||||||
"404": "No contacts matches your search 🔍",
|
"404": "Keine Kontakte entsprechend Deiner Suche gefunden 🔍",
|
||||||
"TABLE_HEADER": [
|
"TABLE_HEADER": [
|
||||||
"Name",
|
"Name",
|
||||||
"Phone Number",
|
"Telefonnummer",
|
||||||
"Gespräche",
|
"Gespräche",
|
||||||
"Last Contacted"
|
"Letzter Kontakt"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,24 +6,25 @@
|
|||||||
"NO_INBOX_1": "Hallo! Sieht so aus, als hätten Sie noch keine Posteingänge hinzugefügt.",
|
"NO_INBOX_1": "Hallo! Sieht so aus, als hätten Sie noch keine Posteingänge hinzugefügt.",
|
||||||
"NO_INBOX_2": " um loszulegen",
|
"NO_INBOX_2": " um loszulegen",
|
||||||
"NO_INBOX_AGENT": "Oh oh! Sieht so aus, als wären Sie nicht Teil eines Posteingangs. Bitte wenden Sie sich an Ihren Administrator",
|
"NO_INBOX_AGENT": "Oh oh! Sieht so aus, als wären Sie nicht Teil eines Posteingangs. Bitte wenden Sie sich an Ihren Administrator",
|
||||||
"SEARCH_MESSAGES": "Search for messages in conversations",
|
"SEARCH_MESSAGES": "Nachrichten durchsuchen",
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"TITLE": "Search messages",
|
"TITLE": "Nachrichten durchsuchen",
|
||||||
"LOADING_MESSAGE": "Crunching data...",
|
"LOADING_MESSAGE": "Daten werden geladen...",
|
||||||
"PLACEHOLDER": "Type any text to search messages",
|
"PLACEHOLDER": "Geben Sie einen Text ein, um danach zu suchen",
|
||||||
"NO_MATCHING_RESULTS": "There are no messages matching the search parameters."
|
"NO_MATCHING_RESULTS": "Keine passenden Nachrichten gefunden."
|
||||||
},
|
},
|
||||||
"UNREAD_MESSAGES": "Unread Messages",
|
"UNREAD_MESSAGES": "Ungelesene Nachrichten",
|
||||||
"UNREAD_MESSAGE": "Unread Message",
|
"UNREAD_MESSAGE": "Ungelesene Nachricht",
|
||||||
"CLICK_HERE": "Hier klicken",
|
"CLICK_HERE": "Hier klicken",
|
||||||
"LOADING_INBOXES": "Posteingänge laden",
|
"LOADING_INBOXES": "Posteingänge laden",
|
||||||
"LOADING_CONVERSATIONS": "Gespräche laden",
|
"LOADING_CONVERSATIONS": "Gespräche laden",
|
||||||
"CANNOT_REPLY": "You cannot reply due to",
|
"CANNOT_REPLY": "Du kannst nicht Antworten aufgrund von",
|
||||||
"24_HOURS_WINDOW": "24 hour message window restriction",
|
"24_HOURS_WINDOW": "24-Stunden-Nachrichtenfenster-Beschränkung",
|
||||||
"LAST_INCOMING_TWEET": "You are replying to the last incoming tweet",
|
"LAST_INCOMING_TWEET": "Du antwortest auf den letzten eingehenden Tweet",
|
||||||
"REPLYING_TO": "You are replying to:",
|
"REPLYING_TO": "Du antwortest auf:",
|
||||||
"REMOVE_SELECTION": "Remove Selection",
|
"REMOVE_SELECTION": "Auswahl entfernen",
|
||||||
"DOWNLOAD": "Herunterladen",
|
"DOWNLOAD": "Herunterladen",
|
||||||
|
"UPLOADING_ATTACHMENTS": "Uploading attachments...",
|
||||||
"HEADER": {
|
"HEADER": {
|
||||||
"RESOLVE_ACTION": "Fall schließen",
|
"RESOLVE_ACTION": "Fall schließen",
|
||||||
"REOPEN_ACTION": "Wieder öffnen",
|
"REOPEN_ACTION": "Wieder öffnen",
|
||||||
@ -40,31 +41,34 @@
|
|||||||
"PRIVATE_NOTE": "Private Notiz",
|
"PRIVATE_NOTE": "Private Notiz",
|
||||||
"SEND": "Senden",
|
"SEND": "Senden",
|
||||||
"CREATE": "Notiz hinzufügen",
|
"CREATE": "Notiz hinzufügen",
|
||||||
"TWEET": "Tweet"
|
"TWEET": "Tweet",
|
||||||
|
"TIP_FORMAT_ICON": "Show rich text editor",
|
||||||
|
"TIP_EMOJI_ICON": "Show emoji selector",
|
||||||
|
"TIP_ATTACH_ICON": "Attach files"
|
||||||
},
|
},
|
||||||
"VISIBLE_TO_AGENTS": "Privater Hinweis: Nur für Sie und Ihr Team sichtbar",
|
"VISIBLE_TO_AGENTS": "Privater Hinweis: Nur für Sie und Ihr Team sichtbar",
|
||||||
"CHANGE_STATUS": "Gesprächsstatus geändert",
|
"CHANGE_STATUS": "Gesprächsstatus geändert",
|
||||||
"CHANGE_AGENT": "Konversationsempfänger geändert",
|
"CHANGE_AGENT": "Konversationsempfänger geändert",
|
||||||
"SENT_BY": "Sent by:",
|
"SENT_BY": "Gesendet von:",
|
||||||
"ASSIGNMENT": {
|
"ASSIGNMENT": {
|
||||||
"SELECT_AGENT": "Select Agent",
|
"SELECT_AGENT": "Agent auswählen",
|
||||||
"REMOVE": "Entfernen",
|
"REMOVE": "Entfernen",
|
||||||
"ASSIGN": "Assign"
|
"ASSIGN": "Zuordnen"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"EMAIL_TRANSCRIPT": {
|
"EMAIL_TRANSCRIPT": {
|
||||||
"TITLE": "Send conversation transcript",
|
"TITLE": "Konversations-Transkript senden",
|
||||||
"DESC": "Send a copy of the conversation transcript to the specified email address",
|
"DESC": "Kopie des Konversationsprotokolls an die angegebene E-Mail-Adresse senden",
|
||||||
"SUBMIT": "Einreichen",
|
"SUBMIT": "Einreichen",
|
||||||
"CANCEL": "Stornieren",
|
"CANCEL": "Stornieren",
|
||||||
"SEND_EMAIL_SUCCESS": "The chat transcript was sent successfully",
|
"SEND_EMAIL_SUCCESS": "Das Chat-Protokoll wurde erfolgreich gesendet",
|
||||||
"SEND_EMAIL_ERROR": "There was an error, please try again",
|
"SEND_EMAIL_ERROR": "Es ist ein Fehler aufgetreten, bitte versuche es erneut",
|
||||||
"FORM": {
|
"FORM": {
|
||||||
"SEND_TO_CONTACT": "Send the transcript to the customer",
|
"SEND_TO_CONTACT": "Das Transkript an den Kunden senden",
|
||||||
"SEND_TO_AGENT": "Send the transcript to the assigned agent",
|
"SEND_TO_AGENT": "Transkript an den zugewiesenen Agent senden",
|
||||||
"SEND_TO_OTHER_EMAIL_ADDRESS": "Send the transcript to another email address",
|
"SEND_TO_OTHER_EMAIL_ADDRESS": "Transkript an eine andere E-Mail-Adresse senden",
|
||||||
"EMAIL": {
|
"EMAIL": {
|
||||||
"PLACEHOLDER": "Enter an email address",
|
"PLACEHOLDER": "E-Mail-Adresse eingeben",
|
||||||
"ERROR": "Bitte geben Sie eine gültige E-Mail-Adresse ein"
|
"ERROR": "Bitte geben Sie eine gültige E-Mail-Adresse ein"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
"GENERAL_SETTINGS": {
|
"GENERAL_SETTINGS": {
|
||||||
"TITLE": "Kontoeinstellungen",
|
"TITLE": "Kontoeinstellungen",
|
||||||
"SUBMIT": "Update Einstellungen",
|
"SUBMIT": "Update Einstellungen",
|
||||||
"BACK": "Back",
|
"BACK": "Zurück",
|
||||||
"UPDATE": {
|
"UPDATE": {
|
||||||
"ERROR": "Einstellungen konnten nicht aktualisiert werden, versuchen Sie es erneut!",
|
"ERROR": "Einstellungen konnten nicht aktualisiert werden, versuchen Sie es erneut!",
|
||||||
"SUCCESS": "Kontoeinstellungen erfolgreich aktualisiert"
|
"SUCCESS": "Kontoeinstellungen erfolgreich aktualisiert"
|
||||||
@ -24,31 +24,32 @@
|
|||||||
"ERROR": ""
|
"ERROR": ""
|
||||||
},
|
},
|
||||||
"DOMAIN": {
|
"DOMAIN": {
|
||||||
"LABEL": "Incoming Email Domain",
|
"LABEL": "Eingehende E-Mail-Domain",
|
||||||
"PLACEHOLDER": "The domain where you will receive the emails",
|
"PLACEHOLDER": "Die Domain, von der E-Mails empfangen werden",
|
||||||
"ERROR": ""
|
"ERROR": ""
|
||||||
},
|
},
|
||||||
"SUPPORT_EMAIL": {
|
"SUPPORT_EMAIL": {
|
||||||
"LABEL": "Support Email",
|
"LABEL": "Support-E-Mail",
|
||||||
"PLACEHOLDER": "Your company's support email",
|
"PLACEHOLDER": "Support E-Mail deines Unternehmens",
|
||||||
"ERROR": ""
|
"ERROR": ""
|
||||||
},
|
},
|
||||||
"AUTO_RESOLVE_DURATION": {
|
"AUTO_RESOLVE_DURATION": {
|
||||||
"LABEL": "Number of days after a ticket should auto resolve if there is no activity",
|
"LABEL": "Anzahl der Tage, nach denen ein Ticket automatisch geschlossen wird, wenn keine Aktivität erfolgt",
|
||||||
"PLACEHOLDER": "30",
|
"PLACEHOLDER": "30",
|
||||||
"ERROR": "Please enter a valid auto resolve duration (minimum 1 day)"
|
"ERROR": "Bitte gebe eine gültige automatische Auflösungsdauer ein (mindestens 1 Tag)"
|
||||||
},
|
},
|
||||||
"FEATURES": {
|
"FEATURES": {
|
||||||
"INBOUND_EMAIL_ENABLED": "Conversation continuity with emails is enabled for your account.",
|
"INBOUND_EMAIL_ENABLED": "Conversation continuity with emails is enabled for your account.",
|
||||||
"CUSTOM_EMAIL_DOMAIN_ENABLED": "You can receive emails in your custom domain now."
|
"CUSTOM_EMAIL_DOMAIN_ENABLED": "Du kannst E-Mails jetzt von der festgelegten Domain erhalten."
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
"UPDATE_CHATWOOT": "An update %{latestChatwootVersion} for Chatwoot is available. Please update your instance."
|
||||||
},
|
},
|
||||||
"FORMS": {
|
"FORMS": {
|
||||||
"MULTISELECT": {
|
"MULTISELECT": {
|
||||||
"ENTER_TO_SELECT": "Press enter to select",
|
"ENTER_TO_SELECT": "Drücke Enter zum Auswählen",
|
||||||
"ENTER_TO_REMOVE": "Press enter to remove",
|
"ENTER_TO_REMOVE": "Drücke Enter zum Entfernen",
|
||||||
"SELECT_ONE": "Select one"
|
"SELECT_ONE": "Eine Option wählen"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"INBOX_MGMT": {
|
"INBOX_MGMT": {
|
||||||
"HEADER": "Posteingänge",
|
"HEADER": "Posteingänge",
|
||||||
"SIDEBAR_TXT": "<p><b>Inbox</b></p> <p> When you connect a website or a facebook Page to Chatwoot, it is called an <b>Inbox</b>. You can have unlimited inboxes in your Chatwoot account. </p><p> Click on <b>Add Inbox</b> to connect a website or a Facebook Page. </p><p> In the Dashboard, you can see all the conversations from all your inboxes in a single place and respond to them under the `Conversations` tab. </p><p> You can also see conversations specific to an inbox by clicking on the inbox name on the left pane of the dashboard. </p>",
|
"SIDEBAR_TXT": "<p><b>Posteingang</b></p> <p> Wenn du eine Webseite oder eine Facebook-Seite mit Chatwood verbindest, wird dieser Kanal als <b>Posteingang</b> bezeichnet. Dabei können unbegrenzte viele Posteingänge angelegt werden. </p><p> Klicke auf <b>Posteingang hinzufügen</b>, um eine Webseite oder eine Facebook-Seite zu verbinden. </p><p> Im Dashboard du kannst alle Unterhaltungen aus allen deinen Posteingängen an einem Ort sehen und unter der Registerkarte \"Unterhaltungen\" beantworten. </p><p> Du kannst dir auch nur Konversationen anzeigen lassen, die in einen bestimmten Posteingang enthalten sind, indem du auf den Posteingangsnamen auf der linken Seite des Dashboards klicken. </p>",
|
||||||
"LIST": {
|
"LIST": {
|
||||||
"404": "Diesem Konto sind keine Posteingänge zugeordnet."
|
"404": "Diesem Konto sind keine Posteingänge zugeordnet."
|
||||||
},
|
},
|
||||||
@ -30,12 +30,12 @@
|
|||||||
"ADD": {
|
"ADD": {
|
||||||
"FB": {
|
"FB": {
|
||||||
"HELP": "PS: Durch die Anmeldung erhalten wir nur Zugriff auf die Nachrichten Ihrer Seite. Auf Ihre privaten Nachrichten kann Chatwoot niemals zugreifen.",
|
"HELP": "PS: Durch die Anmeldung erhalten wir nur Zugriff auf die Nachrichten Ihrer Seite. Auf Ihre privaten Nachrichten kann Chatwoot niemals zugreifen.",
|
||||||
"CHOOSE_PAGE": "Choose Page",
|
"CHOOSE_PAGE": "Seite auswählen",
|
||||||
"CHOOSE_PLACEHOLDER": "Select a page from the list",
|
"CHOOSE_PLACEHOLDER": "Wähle eine Seite aus der Liste",
|
||||||
"INBOX_NAME": "Inbox Name",
|
"INBOX_NAME": "Name des Posteingang",
|
||||||
"ADD_NAME": "Add a name for your inbox",
|
"ADD_NAME": "Namen für diesen Posteingang eingeben",
|
||||||
"PICK_NAME": "Pick A Name Your Inbox",
|
"PICK_NAME": "Wähle einen Namen für deinen Posteingang",
|
||||||
"PICK_A_VALUE": "Pick a value"
|
"PICK_A_VALUE": "Wähle einen Wert"
|
||||||
},
|
},
|
||||||
"TWITTER": {
|
"TWITTER": {
|
||||||
"HELP": "Um Ihr Twitter-Profil als Kanal hinzuzufügen, müssen Sie Ihr Twitter-Profil authentifizieren, indem Sie auf 'Mit Twitter anmelden' klicken."
|
"HELP": "Um Ihr Twitter-Profil als Kanal hinzuzufügen, müssen Sie Ihr Twitter-Profil authentifizieren, indem Sie auf 'Mit Twitter anmelden' klicken."
|
||||||
@ -43,7 +43,7 @@
|
|||||||
"WEBSITE_CHANNEL": {
|
"WEBSITE_CHANNEL": {
|
||||||
"TITLE": "Website-Kanal",
|
"TITLE": "Website-Kanal",
|
||||||
"DESC": "Erstellen Sie einen Kanal für Ihre Website und unterstützen Sie Ihre Kunden über unser Website-Widget.",
|
"DESC": "Erstellen Sie einen Kanal für Ihre Website und unterstützen Sie Ihre Kunden über unser Website-Widget.",
|
||||||
"LOADING_MESSAGE": "Creating Website Support Channel",
|
"LOADING_MESSAGE": "Website-Support-Channel erstellen",
|
||||||
"CHANNEL_AVATAR": {
|
"CHANNEL_AVATAR": {
|
||||||
"LABEL": "Channel Avatar"
|
"LABEL": "Channel Avatar"
|
||||||
},
|
},
|
||||||
@ -52,33 +52,33 @@
|
|||||||
"PLACEHOLDER": "Geben Sie den Namen Ihrer Website ein (eg: Acme Inc)"
|
"PLACEHOLDER": "Geben Sie den Namen Ihrer Website ein (eg: Acme Inc)"
|
||||||
},
|
},
|
||||||
"CHANNEL_DOMAIN": {
|
"CHANNEL_DOMAIN": {
|
||||||
"LABEL": "Website Domain",
|
"LABEL": "Website-Domain",
|
||||||
"PLACEHOLDER": "Geben Sie Ihre Website-Domain ein (eg: acme.com)"
|
"PLACEHOLDER": "Geben Sie Ihre Website-Domain ein (eg: acme.com)"
|
||||||
},
|
},
|
||||||
"CHANNEL_WELCOME_TITLE": {
|
"CHANNEL_WELCOME_TITLE": {
|
||||||
"LABEL": "Welcome Heading",
|
"LABEL": "Willkommens-Überschrift",
|
||||||
"PLACEHOLDER": "Hi there !"
|
"PLACEHOLDER": "Hallo!"
|
||||||
},
|
},
|
||||||
"CHANNEL_WELCOME_TAGLINE": {
|
"CHANNEL_WELCOME_TAGLINE": {
|
||||||
"LABEL": "Welcome Tagline",
|
"LABEL": "Willkommens-Schlagzeile",
|
||||||
"PLACEHOLDER": "We make it simple to connect with us. Ask us anything, or share your feedback."
|
"PLACEHOLDER": "Wir machen es einfach, mit uns in Verbindung zu treten. Fragen Sie uns etwas oder teilen Sie Ihr Feedback."
|
||||||
},
|
},
|
||||||
"CHANNEL_GREETING_MESSAGE": {
|
"CHANNEL_GREETING_MESSAGE": {
|
||||||
"LABEL": "Channel greeting message",
|
"LABEL": "Grußnachricht des Kanals",
|
||||||
"PLACEHOLDER": "Acme Inc typically replies in a few hours."
|
"PLACEHOLDER": "Wir antworten in der Regel in wenigen Stunden."
|
||||||
},
|
},
|
||||||
"CHANNEL_GREETING_TOGGLE": {
|
"CHANNEL_GREETING_TOGGLE": {
|
||||||
"LABEL": "Enable channel greeting",
|
"LABEL": "Kanal Begrüßung aktivieren",
|
||||||
"HELP_TEXT": "Send a greeting message to the user when he starts the conversation.",
|
"HELP_TEXT": "Senden Sie eine Grußnachricht an den Benutzer, wenn er die Unterhaltung beginnt.",
|
||||||
"ENABLED": "Aktiviert",
|
"ENABLED": "Aktiviert",
|
||||||
"DISABLED": "Behindert"
|
"DISABLED": "Behindert"
|
||||||
},
|
},
|
||||||
"REPLY_TIME": {
|
"REPLY_TIME": {
|
||||||
"TITLE": "Reaktionszeit festlegen",
|
"TITLE": "Reaktionszeit festlegen",
|
||||||
"IN_A_FEW_MINUTES": "In a few minutes",
|
"IN_A_FEW_MINUTES": "Innerhalb weniger Minuten",
|
||||||
"IN_A_FEW_HOURS": "In a few hours",
|
"IN_A_FEW_HOURS": "Innerhalb weniger Stunden",
|
||||||
"IN_A_DAY": "In a day",
|
"IN_A_DAY": "Innerhalb eines Tages",
|
||||||
"HELP_TEXT": "This reply time will be displayed on the live chat widget"
|
"HELP_TEXT": "Diese Antwortzeit wird im Live-Chat-Widget angezeigt"
|
||||||
},
|
},
|
||||||
"WIDGET_COLOR": {
|
"WIDGET_COLOR": {
|
||||||
"LABEL": "Widget Farbe",
|
"LABEL": "Widget Farbe",
|
||||||
@ -87,7 +87,7 @@
|
|||||||
"SUBMIT_BUTTON": "Posteingang erstellen"
|
"SUBMIT_BUTTON": "Posteingang erstellen"
|
||||||
},
|
},
|
||||||
"TWILIO": {
|
"TWILIO": {
|
||||||
"TITLE": "Twilio SMS/Whatsapp Channel",
|
"TITLE": "Twilio SMS/Whatsapp Kanal",
|
||||||
"DESC": "Integrieren Sie Twilio und unterstützen Sie Ihre Kunden per SMS/Whatsapp.",
|
"DESC": "Integrieren Sie Twilio und unterstützen Sie Ihre Kunden per SMS/Whatsapp.",
|
||||||
"ACCOUNT_SID": {
|
"ACCOUNT_SID": {
|
||||||
"LABEL": "Account SID",
|
"LABEL": "Account SID",
|
||||||
@ -95,11 +95,11 @@
|
|||||||
"ERROR": "Dieses Feld wird benötigt"
|
"ERROR": "Dieses Feld wird benötigt"
|
||||||
},
|
},
|
||||||
"CHANNEL_TYPE": {
|
"CHANNEL_TYPE": {
|
||||||
"LABEL": "Channel Type",
|
"LABEL": "Kanal-Typ",
|
||||||
"ERROR": "Please select your Channel Type"
|
"ERROR": "Bitte wählen Sie den Kanal-Typ aus"
|
||||||
},
|
},
|
||||||
"AUTH_TOKEN": {
|
"AUTH_TOKEN": {
|
||||||
"LABEL": "Auth Token",
|
"LABEL": "Auth-Token",
|
||||||
"PLACEHOLDER": "Bitte geben Sie Ihr Twilio Auth Token ein",
|
"PLACEHOLDER": "Bitte geben Sie Ihr Twilio Auth Token ein",
|
||||||
"ERROR": "Dieses Feld wird benötigt"
|
"ERROR": "Dieses Feld wird benötigt"
|
||||||
},
|
},
|
||||||
@ -115,7 +115,7 @@
|
|||||||
},
|
},
|
||||||
"API_CALLBACK": {
|
"API_CALLBACK": {
|
||||||
"TITLE": "Callback URL",
|
"TITLE": "Callback URL",
|
||||||
"SUBTITLE": "You have to configure the message callback URL in Twilio with the URL mentioned here."
|
"SUBTITLE": "Sie müssen die Callback-URL in Twilio mit der hier genannten URL konfigurieren."
|
||||||
},
|
},
|
||||||
"SUBMIT_BUTTON": "Erstellen Sie Twilio Channel",
|
"SUBMIT_BUTTON": "Erstellen Sie Twilio Channel",
|
||||||
"API": {
|
"API": {
|
||||||
@ -123,8 +123,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"API_CHANNEL": {
|
"API_CHANNEL": {
|
||||||
"TITLE": "API Channel",
|
"TITLE": "API-Kanal",
|
||||||
"DESC": "Integrate with API channel and start supporting your customers.",
|
"DESC": "Integrieren Sie einen API-Kanal und starten Sie mit der Unterstützung Ihrer Kunden.",
|
||||||
"CHANNEL_NAME": {
|
"CHANNEL_NAME": {
|
||||||
"LABEL": "Kanal Name",
|
"LABEL": "Kanal Name",
|
||||||
"PLACEHOLDER": "Bitte geben Sie einen Kanalnamen ein",
|
"PLACEHOLDER": "Bitte geben Sie einen Kanalnamen ein",
|
||||||
@ -132,32 +132,32 @@
|
|||||||
},
|
},
|
||||||
"WEBHOOK_URL": {
|
"WEBHOOK_URL": {
|
||||||
"LABEL": "Webhook-URL",
|
"LABEL": "Webhook-URL",
|
||||||
"SUBTITLE": "Configure the URL where you want to recieve callbacks on events.",
|
"SUBTITLE": "Konfigurieren Sie die URL, auf der Sie Callbacks bei Events erhalten möchten.",
|
||||||
"PLACEHOLDER": "Webhook-URL"
|
"PLACEHOLDER": "Webhook-URL"
|
||||||
},
|
},
|
||||||
"SUBMIT_BUTTON": "Create API Channel",
|
"SUBMIT_BUTTON": "API-Kanal erstellen",
|
||||||
"API": {
|
"API": {
|
||||||
"ERROR_MESSAGE": "We were not able to save the api channel"
|
"ERROR_MESSAGE": "Der API-Kanal konnte nicht gespeichert werden"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"EMAIL_CHANNEL": {
|
"EMAIL_CHANNEL": {
|
||||||
"TITLE": "Email Channel",
|
"TITLE": "E-Mail-Kanal",
|
||||||
"DESC": "Integrate you email inbox.",
|
"DESC": "Integrieren Sie Ihren Posteingang.",
|
||||||
"CHANNEL_NAME": {
|
"CHANNEL_NAME": {
|
||||||
"LABEL": "Kanal Name",
|
"LABEL": "Kanal Name",
|
||||||
"PLACEHOLDER": "Bitte geben Sie einen Kanalnamen ein",
|
"PLACEHOLDER": "Bitte geben Sie einen Kanalnamen ein",
|
||||||
"ERROR": "Dieses Feld wird benötigt"
|
"ERROR": "Dieses Feld wird benötigt"
|
||||||
},
|
},
|
||||||
"EMAIL": {
|
"EMAIL": {
|
||||||
"LABEL": "Email",
|
"LABEL": "E-Mail",
|
||||||
"SUBTITLE": "Email where your customers sends you support tickets",
|
"SUBTITLE": "E-Mail Adresse, an die Ihre Kunden Ihnen Support-Tickets senden",
|
||||||
"PLACEHOLDER": "Email"
|
"PLACEHOLDER": "E-Mail"
|
||||||
},
|
},
|
||||||
"SUBMIT_BUTTON": "Create Email Channel",
|
"SUBMIT_BUTTON": "E-Mail-Kanal erstellen",
|
||||||
"API": {
|
"API": {
|
||||||
"ERROR_MESSAGE": "We were not able to save the email channel"
|
"ERROR_MESSAGE": "Wir konnten den E-Mail-Kanal nicht speichern"
|
||||||
},
|
},
|
||||||
"FINISH_MESSAGE": "Start forwarding your emails to the following email address."
|
"FINISH_MESSAGE": "Starten Sie die Weiterleitung Ihrer E-Mails an die folgende E-Mail-Adresse."
|
||||||
},
|
},
|
||||||
"AUTH": {
|
"AUTH": {
|
||||||
"TITLE": "Kanäle",
|
"TITLE": "Kanäle",
|
||||||
@ -166,8 +166,8 @@
|
|||||||
"AGENTS": {
|
"AGENTS": {
|
||||||
"TITLE": "Agenten",
|
"TITLE": "Agenten",
|
||||||
"DESC": "Hier können Sie Agenten hinzufügen, um Ihren neu erstellten Posteingang zu verwalten. Nur diese ausgewählten Agenten haben Zugriff auf Ihren Posteingang. Agenten, die nicht Teil dieses Posteingangs sind, können bei der Anmeldung keine Nachrichten in diesem Posteingang sehen oder darauf antworten. <br> <b> PS: </b> Wenn Sie als Administrator Zugriff auf alle Posteingänge benötigen, sollten Sie sich als Agent zu allen von Ihnen erstellten Posteingängen hinzufügen.",
|
"DESC": "Hier können Sie Agenten hinzufügen, um Ihren neu erstellten Posteingang zu verwalten. Nur diese ausgewählten Agenten haben Zugriff auf Ihren Posteingang. Agenten, die nicht Teil dieses Posteingangs sind, können bei der Anmeldung keine Nachrichten in diesem Posteingang sehen oder darauf antworten. <br> <b> PS: </b> Wenn Sie als Administrator Zugriff auf alle Posteingänge benötigen, sollten Sie sich als Agent zu allen von Ihnen erstellten Posteingängen hinzufügen.",
|
||||||
"VALIDATION_ERROR": "Add atleast one agent to your new Inbox",
|
"VALIDATION_ERROR": "Fügen Sie mindestens einen Agenten zu Ihrem neuen Posteingang hinzu",
|
||||||
"PICK_AGENTS": "Pick agents for the inbox"
|
"PICK_AGENTS": "Agenten für den Posteingang auswählen"
|
||||||
},
|
},
|
||||||
"DETAILS": {
|
"DETAILS": {
|
||||||
"TITLE": "Posteingangsdetails",
|
"TITLE": "Posteingangsdetails",
|
||||||
@ -223,14 +223,14 @@
|
|||||||
},
|
},
|
||||||
"TABS": {
|
"TABS": {
|
||||||
"SETTINGS": "die Einstellungen",
|
"SETTINGS": "die Einstellungen",
|
||||||
"COLLABORATORS": "Collaborators",
|
"COLLABORATORS": "Mitarbeitende",
|
||||||
"CONFIGURATION": "Configuration"
|
"CONFIGURATION": "Konfiguration"
|
||||||
},
|
},
|
||||||
"SETTINGS": "die Einstellungen",
|
"SETTINGS": "die Einstellungen",
|
||||||
"FEATURES": {
|
"FEATURES": {
|
||||||
"LABEL": "Features",
|
"LABEL": "Funktionen",
|
||||||
"DISPLAY_FILE_PICKER": "Display file picker on the widget",
|
"DISPLAY_FILE_PICKER": "Dateiauswahl im Widget anzeigen",
|
||||||
"DISPLAY_EMOJI_PICKER": "Display emoji picker on the widget"
|
"DISPLAY_EMOJI_PICKER": "Emoji-Auswahl im Widget anzeigen"
|
||||||
},
|
},
|
||||||
"SETTINGS_POPUP": {
|
"SETTINGS_POPUP": {
|
||||||
"MESSENGER_HEADING": "Messenger-Skript",
|
"MESSENGER_HEADING": "Messenger-Skript",
|
||||||
@ -239,15 +239,17 @@
|
|||||||
"INBOX_AGENTS_SUB_TEXT": "Hinzufügen oder Entfernen von Agenten zu diesem Posteingang",
|
"INBOX_AGENTS_SUB_TEXT": "Hinzufügen oder Entfernen von Agenten zu diesem Posteingang",
|
||||||
"UPDATE": "Aktualisieren",
|
"UPDATE": "Aktualisieren",
|
||||||
"AUTO_ASSIGNMENT": "Aktivieren Sie die automatische Zuweisung",
|
"AUTO_ASSIGNMENT": "Aktivieren Sie die automatische Zuweisung",
|
||||||
"INBOX_UPDATE_TITLE": "Inbox Settings",
|
"INBOX_UPDATE_TITLE": "Posteingang Einstellungen",
|
||||||
"INBOX_UPDATE_SUB_TEXT": "Update your inbox settings",
|
"INBOX_UPDATE_SUB_TEXT": "Posteingang Einstellungen aktualisieren",
|
||||||
"AUTO_ASSIGNMENT_SUB_TEXT": "Aktivieren oder deaktivieren Sie die automatische Zuweisung verfügbarer Agenten für neue Konversationen"
|
"AUTO_ASSIGNMENT_SUB_TEXT": "Aktivieren oder deaktivieren Sie die automatische Zuweisung verfügbarer Agenten für neue Konversationen",
|
||||||
|
"HMAC_VERIFICATION": "User Identity Validation",
|
||||||
|
"HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here."
|
||||||
},
|
},
|
||||||
"FACEBOOK_REAUTHORIZE": {
|
"FACEBOOK_REAUTHORIZE": {
|
||||||
"TITLE": "Neu autorisieren",
|
"TITLE": "Neu autorisieren",
|
||||||
"SUBTITLE": "Your Facebook connection has expired, please reconnect your Facebook page to continue services",
|
"SUBTITLE": "Ihre Facebook-Verbindung ist abgelaufen, bitte verbinden Sie sich neu, um die Dienste fortzuführen",
|
||||||
"MESSAGE_SUCCESS": "Reconnection successful",
|
"MESSAGE_SUCCESS": "Wiederverbindung erfolgreich",
|
||||||
"MESSAGE_ERROR": "There was an error, please try again"
|
"MESSAGE_ERROR": "Es ist ein Fehler aufgetreten, bitte versuche es erneut"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
/* eslint-disable */
|
|
||||||
import { default as _agentMgmt } from './agentMgmt.json';
|
import { default as _agentMgmt } from './agentMgmt.json';
|
||||||
|
import { default as _labelsMgmt } from './labelsMgmt.json';
|
||||||
import { default as _cannedMgmt } from './cannedMgmt.json';
|
import { default as _cannedMgmt } from './cannedMgmt.json';
|
||||||
import { default as _chatlist } from './chatlist.json';
|
import { default as _chatlist } from './chatlist.json';
|
||||||
import { default as _contact } from './contact.json';
|
import { default as _contact } from './contact.json';
|
||||||
@ -23,6 +23,7 @@ export default {
|
|||||||
..._inboxMgmt,
|
..._inboxMgmt,
|
||||||
..._login,
|
..._login,
|
||||||
..._report,
|
..._report,
|
||||||
|
..._labelsMgmt,
|
||||||
..._resetPassword,
|
..._resetPassword,
|
||||||
..._setNewPassword,
|
..._setNewPassword,
|
||||||
..._settings,
|
..._settings,
|
||||||
|
|||||||
@ -24,7 +24,7 @@
|
|||||||
"FORM": {
|
"FORM": {
|
||||||
"END_POINT": {
|
"END_POINT": {
|
||||||
"LABEL": "Webhook-URL",
|
"LABEL": "Webhook-URL",
|
||||||
"PLACEHOLDER": "Example: https://example/api/webhook",
|
"PLACEHOLDER": "Beispiel: https://example/api/webhook",
|
||||||
"ERROR": "Bitte geben Sie eine gültige URL ein"
|
"ERROR": "Bitte geben Sie eine gültige URL ein"
|
||||||
},
|
},
|
||||||
"SUBMIT": "Webhook erstellen"
|
"SUBMIT": "Webhook erstellen"
|
||||||
@ -51,11 +51,11 @@
|
|||||||
"DELETE": {
|
"DELETE": {
|
||||||
"BUTTON_TEXT": "Löschen",
|
"BUTTON_TEXT": "Löschen",
|
||||||
"API": {
|
"API": {
|
||||||
"SUCCESS_MESSAGE": "Integration deleted successfully"
|
"SUCCESS_MESSAGE": "Integration erfolgreich gelöscht"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"CONNECT": {
|
"CONNECT": {
|
||||||
"BUTTON_TEXT": "Connect"
|
"BUTTON_TEXT": "Verbinden"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,8 +2,8 @@
|
|||||||
"LOGIN": {
|
"LOGIN": {
|
||||||
"TITLE": "Melden Sie sich bei Chatwoot an",
|
"TITLE": "Melden Sie sich bei Chatwoot an",
|
||||||
"EMAIL": {
|
"EMAIL": {
|
||||||
"LABEL": "Email",
|
"LABEL": "E-Mail",
|
||||||
"PLACEHOLDER": "Email eg: someone@example.com"
|
"PLACEHOLDER": "E-Mail z.B. jemand@example.com"
|
||||||
},
|
},
|
||||||
"PASSWORD": {
|
"PASSWORD": {
|
||||||
"LABEL": "Passwort",
|
"LABEL": "Passwort",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user