Merge branch 'release/1.20.0'
This commit is contained in:
commit
b474e39feb
737 changed files with 18270 additions and 3396 deletions
|
@ -7,7 +7,7 @@ defaults: &defaults
|
|||
working_directory: ~/build
|
||||
docker:
|
||||
# specify the version you desire here
|
||||
- image: circleci/ruby:3.0.2-node-browsers
|
||||
- image: cimg/ruby:3.0.2-node
|
||||
|
||||
# Specify service dependencies here if necessary
|
||||
# CircleCI maintains a library of pre-built images
|
||||
|
@ -40,20 +40,20 @@ jobs:
|
|||
|
||||
- restore_cache:
|
||||
keys:
|
||||
- chatwoot-bundle-{{ checksum "Gemfile.lock" }}
|
||||
- chatwoot-bundle-{{ .Environment.CACHE_VERSION }}-{{ checksum "Gemfile.lock" }}
|
||||
- chatwoot-bundle
|
||||
|
||||
- run: bundle install --frozen --path ~/.bundle
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.bundle
|
||||
key: chatwoot-bundle-{{ checksum "Gemfile.lock" }}
|
||||
key: chatwoot-bundle-{{ .Environment.CACHE_VERSION }}-{{ checksum "Gemfile.lock" }}
|
||||
|
||||
|
||||
# Only necessary if app uses webpacker or yarn in some other way
|
||||
- restore_cache:
|
||||
keys:
|
||||
- chatwoot-yarn-{{ checksum "yarn.lock" }}
|
||||
- chatwoot-yarn-{{ .Environment.CACHE_VERSION }}-{{ checksum "yarn.lock" }}
|
||||
- chatwoot-yarn-
|
||||
|
||||
- run:
|
||||
|
@ -62,7 +62,7 @@ jobs:
|
|||
|
||||
# Store yarn / webpacker cache
|
||||
- save_cache:
|
||||
key: chatwoot-yarn-{{ checksum "yarn.lock" }}
|
||||
key: chatwoot-yarn-{{ .Environment.CACHE_VERSION }}-{{ checksum "yarn.lock" }}
|
||||
paths:
|
||||
- ~/.cache/yarn
|
||||
|
||||
|
|
21
.env.example
21
.env.example
|
@ -85,8 +85,6 @@ AWS_ACCESS_KEY_ID=
|
|||
AWS_SECRET_ACCESS_KEY=
|
||||
AWS_REGION=
|
||||
|
||||
# Sentry
|
||||
SENTRY_DSN=
|
||||
|
||||
# Log settings
|
||||
# Disable if you want to write logs to a file
|
||||
|
@ -139,6 +137,25 @@ ANDROID_SHA256_CERT_FINGERPRINT=AC:73:8E:DE:EB:56:EA:CC:10:87:02:A7:65:37:7B:38:
|
|||
USE_INBOX_AVATAR_FOR_BOT=true
|
||||
|
||||
|
||||
### APM and Error Monitoring configurations
|
||||
## Sentry
|
||||
# SENTRY_DSN=
|
||||
|
||||
## Scout
|
||||
## https://scoutapm.com/docs/ruby/configuration
|
||||
# SCOUT_KEY=YOURKEY
|
||||
# SCOUT_NAME=YOURAPPNAME (Production)
|
||||
# SCOUT_MONITOR=true
|
||||
|
||||
## NewRelic
|
||||
# https://docs.newrelic.com/docs/agents/ruby-agent/configuration/ruby-agent-configuration/
|
||||
# NEW_RELIC_LICENSE_KEY=
|
||||
|
||||
## Datadog
|
||||
## https://github.com/DataDog/dd-trace-rb/blob/master/docs/GettingStarted.md#environment-variables
|
||||
# DD_TRACE_AGENT_URL=
|
||||
|
||||
|
||||
|
||||
## IP look up configuration
|
||||
## ref https://github.com/alexreisner/geocoder/blob/master/README_API_GUIDE.md
|
||||
|
|
|
@ -100,6 +100,7 @@ Metrics/AbcSize:
|
|||
- 'app/controllers/concerns/auth_helper.rb'
|
||||
- 'db/migrate/20190819005836_add_missing_indexes_on_taggings.acts_as_taggable_on_engine.rb'
|
||||
- 'db/migrate/20161123131628_devise_token_auth_create_users.rb'
|
||||
- 'app/controllers/api/v1/accounts/inboxes_controller.rb'
|
||||
Metrics/CyclomaticComplexity:
|
||||
Max: 7
|
||||
Exclude:
|
||||
|
|
7
Gemfile
7
Gemfile
|
@ -63,7 +63,7 @@ gem 'barnes'
|
|||
|
||||
##--- gems for authentication & authorization ---##
|
||||
gem 'devise'
|
||||
gem 'devise-secure_password', '~> 2.0'
|
||||
gem 'devise-secure_password', '~> 2.0', git: 'https://github.com/chatwoot/devise-secure_password'
|
||||
gem 'devise_token_auth'
|
||||
# authorization
|
||||
gem 'jwt'
|
||||
|
@ -78,7 +78,7 @@ gem 'wisper', '2.0.0'
|
|||
##--- gems for channels ---##
|
||||
# TODO: bump up gem to 2.0
|
||||
gem 'facebook-messenger'
|
||||
gem 'telegram-bot-ruby'
|
||||
gem 'line-bot-api'
|
||||
gem 'twilio-ruby', '~> 5.32.0'
|
||||
# twitty will handle subscription of twitter account events
|
||||
# gem 'twitty', git: 'https://github.com/chatwoot/twitty'
|
||||
|
@ -93,7 +93,10 @@ gem 'google-cloud-dialogflow'
|
|||
##--- gems for debugging and error reporting ---##
|
||||
# static analysis
|
||||
gem 'brakeman'
|
||||
|
||||
##-- apm and error monitoring ---#
|
||||
gem 'ddtrace'
|
||||
gem 'newrelic_rpm'
|
||||
gem 'scout_apm'
|
||||
gem 'sentry-rails'
|
||||
gem 'sentry-ruby'
|
||||
|
|
157
Gemfile.lock
157
Gemfile.lock
|
@ -1,63 +1,71 @@
|
|||
GIT
|
||||
remote: https://github.com/chatwoot/devise-secure_password
|
||||
revision: de11e8765654b8242d42101ee9c8ffc8126f7975
|
||||
specs:
|
||||
devise-secure_password (2.0.1)
|
||||
devise (>= 4.0.0, < 5.0.0)
|
||||
railties (>= 5.0.0, < 7.0.0)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (6.1.4)
|
||||
actionpack (= 6.1.4)
|
||||
activesupport (= 6.1.4)
|
||||
actioncable (6.1.4.1)
|
||||
actionpack (= 6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.1.4)
|
||||
actionpack (= 6.1.4)
|
||||
activejob (= 6.1.4)
|
||||
activerecord (= 6.1.4)
|
||||
activestorage (= 6.1.4)
|
||||
activesupport (= 6.1.4)
|
||||
actionmailbox (6.1.4.1)
|
||||
actionpack (= 6.1.4.1)
|
||||
activejob (= 6.1.4.1)
|
||||
activerecord (= 6.1.4.1)
|
||||
activestorage (= 6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.1.4)
|
||||
actionpack (= 6.1.4)
|
||||
actionview (= 6.1.4)
|
||||
activejob (= 6.1.4)
|
||||
activesupport (= 6.1.4)
|
||||
actionmailer (6.1.4.1)
|
||||
actionpack (= 6.1.4.1)
|
||||
actionview (= 6.1.4.1)
|
||||
activejob (= 6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.1.4)
|
||||
actionview (= 6.1.4)
|
||||
activesupport (= 6.1.4)
|
||||
actionpack (6.1.4.1)
|
||||
actionview (= 6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
rack (~> 2.0, >= 2.0.9)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.1.4)
|
||||
actionpack (= 6.1.4)
|
||||
activerecord (= 6.1.4)
|
||||
activestorage (= 6.1.4)
|
||||
activesupport (= 6.1.4)
|
||||
actiontext (6.1.4.1)
|
||||
actionpack (= 6.1.4.1)
|
||||
activerecord (= 6.1.4.1)
|
||||
activestorage (= 6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.1.4)
|
||||
activesupport (= 6.1.4)
|
||||
actionview (6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
active_record_query_trace (1.8)
|
||||
activejob (6.1.4)
|
||||
activesupport (= 6.1.4)
|
||||
activejob (6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.1.4)
|
||||
activesupport (= 6.1.4)
|
||||
activerecord (6.1.4)
|
||||
activemodel (= 6.1.4)
|
||||
activesupport (= 6.1.4)
|
||||
activemodel (6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
activerecord (6.1.4.1)
|
||||
activemodel (= 6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
activerecord-import (1.2.0)
|
||||
activerecord (>= 3.2)
|
||||
activestorage (6.1.4)
|
||||
actionpack (= 6.1.4)
|
||||
activejob (= 6.1.4)
|
||||
activerecord (= 6.1.4)
|
||||
activesupport (= 6.1.4)
|
||||
activestorage (6.1.4.1)
|
||||
actionpack (= 6.1.4.1)
|
||||
activejob (= 6.1.4.1)
|
||||
activerecord (= 6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
marcel (~> 1.0.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (6.1.4)
|
||||
activesupport (6.1.4.1)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
|
@ -98,10 +106,6 @@ GEM
|
|||
aws-sigv4 (~> 1.1)
|
||||
aws-sigv4 (1.2.4)
|
||||
aws-eventstream (~> 1, >= 1.0.2)
|
||||
axiom-types (0.1.1)
|
||||
descendants_tracker (~> 0.0.4)
|
||||
ice_nine (~> 0.11.0)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
azure-storage-blob (2.0.1)
|
||||
azure-storage-common (~> 2.0)
|
||||
nokogiri (~> 1.11.0.rc2)
|
||||
|
@ -115,7 +119,7 @@ GEM
|
|||
statsd-ruby (~> 1.1)
|
||||
bcrypt (3.1.16)
|
||||
bindex (0.8.1)
|
||||
bootsnap (1.7.6)
|
||||
bootsnap (1.7.7)
|
||||
msgpack (~> 1.0)
|
||||
brakeman (5.1.1)
|
||||
browser (5.3.1)
|
||||
|
@ -130,8 +134,6 @@ GEM
|
|||
thor (~> 1.0)
|
||||
byebug (11.1.3)
|
||||
coderay (1.1.3)
|
||||
coercible (1.0.0)
|
||||
descendants_tracker (~> 0.0.1)
|
||||
commonmarker (0.22.0)
|
||||
concurrent-ruby (1.1.9)
|
||||
connection_pool (2.2.5)
|
||||
|
@ -152,17 +154,12 @@ GEM
|
|||
ffi (~> 1.0)
|
||||
msgpack
|
||||
declarative (0.0.20)
|
||||
descendants_tracker (0.0.4)
|
||||
thread_safe (~> 0.3, >= 0.3.1)
|
||||
devise (4.8.0)
|
||||
bcrypt (~> 3.0)
|
||||
orm_adapter (~> 0.1)
|
||||
railties (>= 4.1.0)
|
||||
responders
|
||||
warden (~> 1.2.3)
|
||||
devise-secure_password (2.0.1)
|
||||
devise (>= 4.0.0, < 5.0.0)
|
||||
railties (>= 5.0.0, < 7.0.0)
|
||||
devise_token_auth (1.2.0)
|
||||
bcrypt (~> 3.0)
|
||||
devise (> 3.5.2, < 5)
|
||||
|
@ -179,7 +176,6 @@ GEM
|
|||
railties (>= 3.2)
|
||||
down (5.2.3)
|
||||
addressable (~> 2.8)
|
||||
dry-inflector (0.2.1)
|
||||
ecma-re-validator (0.3.0)
|
||||
regexp_parser (~> 2.0)
|
||||
erubi (1.10.0)
|
||||
|
@ -216,7 +212,7 @@ GEM
|
|||
grpc (~> 1.25)
|
||||
geocoder (1.6.7)
|
||||
gli (2.20.1)
|
||||
globalid (0.5.1)
|
||||
globalid (0.5.2)
|
||||
activesupport (>= 5.0)
|
||||
google-apis-core (0.4.1)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
|
@ -292,7 +288,6 @@ GEM
|
|||
httpclient (2.8.3)
|
||||
i18n (1.8.10)
|
||||
concurrent-ruby (~> 1.0)
|
||||
ice_nine (0.11.2)
|
||||
image_processing (1.12.1)
|
||||
mini_magick (>= 4.9.5, < 5)
|
||||
ruby-vips (>= 2.0.17, < 3)
|
||||
|
@ -332,11 +327,12 @@ GEM
|
|||
addressable (~> 2.7)
|
||||
letter_opener (1.7.0)
|
||||
launchy (~> 2.2)
|
||||
line-bot-api (1.21.0)
|
||||
liquid (5.0.1)
|
||||
listen (3.6.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
loofah (2.11.0)
|
||||
loofah (2.12.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
|
@ -349,7 +345,7 @@ GEM
|
|||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2021.0704)
|
||||
mini_magick (4.11.0)
|
||||
mini_mime (1.1.0)
|
||||
mini_mime (1.1.1)
|
||||
minitest (5.14.4)
|
||||
mock_redis (0.28.0)
|
||||
ruby2_keywords
|
||||
|
@ -362,7 +358,8 @@ GEM
|
|||
net-http-persistent (4.0.1)
|
||||
connection_pool (~> 2.2)
|
||||
netrc (0.11.0)
|
||||
nio4r (2.5.7)
|
||||
newrelic_rpm (7.2.0)
|
||||
nio4r (2.5.8)
|
||||
nokogiri (1.11.7-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.11.7-x86_64-darwin)
|
||||
|
@ -400,29 +397,29 @@ GEM
|
|||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-timeout (0.6.0)
|
||||
rails (6.1.4)
|
||||
actioncable (= 6.1.4)
|
||||
actionmailbox (= 6.1.4)
|
||||
actionmailer (= 6.1.4)
|
||||
actionpack (= 6.1.4)
|
||||
actiontext (= 6.1.4)
|
||||
actionview (= 6.1.4)
|
||||
activejob (= 6.1.4)
|
||||
activemodel (= 6.1.4)
|
||||
activerecord (= 6.1.4)
|
||||
activestorage (= 6.1.4)
|
||||
activesupport (= 6.1.4)
|
||||
rails (6.1.4.1)
|
||||
actioncable (= 6.1.4.1)
|
||||
actionmailbox (= 6.1.4.1)
|
||||
actionmailer (= 6.1.4.1)
|
||||
actionpack (= 6.1.4.1)
|
||||
actiontext (= 6.1.4.1)
|
||||
actionview (= 6.1.4.1)
|
||||
activejob (= 6.1.4.1)
|
||||
activemodel (= 6.1.4.1)
|
||||
activerecord (= 6.1.4.1)
|
||||
activestorage (= 6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 6.1.4)
|
||||
railties (= 6.1.4.1)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.3.0)
|
||||
rails-html-sanitizer (1.4.1)
|
||||
loofah (~> 2.3)
|
||||
railties (6.1.4)
|
||||
actionpack (= 6.1.4)
|
||||
activesupport (= 6.1.4)
|
||||
railties (6.1.4.1)
|
||||
actionpack (= 6.1.4.1)
|
||||
activesupport (= 6.1.4.1)
|
||||
method_source
|
||||
rake (>= 0.13)
|
||||
thor (~> 1.0)
|
||||
|
@ -563,13 +560,8 @@ GEM
|
|||
sprockets (>= 3.0.0)
|
||||
squasher (0.6.2)
|
||||
statsd-ruby (1.5.0)
|
||||
telegram-bot-ruby (0.16.0)
|
||||
dry-inflector
|
||||
faraday
|
||||
virtus (~> 2.0)
|
||||
telephone_number (1.4.12)
|
||||
thor (1.1.0)
|
||||
thread_safe (0.3.6)
|
||||
tilt (2.0.10)
|
||||
time_diff (0.3.0)
|
||||
activesupport
|
||||
|
@ -597,10 +589,6 @@ GEM
|
|||
valid_email2 (4.0.0)
|
||||
activemodel (>= 3.2)
|
||||
mail (~> 2.5)
|
||||
virtus (2.0.0)
|
||||
axiom-types (~> 0.1)
|
||||
coercible (~> 1.0)
|
||||
descendants_tracker (~> 0.0, >= 0.0.3)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
web-console (4.1.0)
|
||||
|
@ -629,6 +617,8 @@ GEM
|
|||
|
||||
PLATFORMS
|
||||
arm64-darwin-20
|
||||
x86_64-darwin-18
|
||||
x86_64-darwin-20
|
||||
x86_64-darwin-21
|
||||
x86_64-linux
|
||||
|
||||
|
@ -653,7 +643,7 @@ DEPENDENCIES
|
|||
database_cleaner
|
||||
ddtrace
|
||||
devise
|
||||
devise-secure_password (~> 2.0)
|
||||
devise-secure_password (~> 2.0)!
|
||||
devise_token_auth
|
||||
dotenv-rails
|
||||
down (~> 5.0)
|
||||
|
@ -678,10 +668,12 @@ DEPENDENCIES
|
|||
kaminari
|
||||
koala
|
||||
letter_opener
|
||||
line-bot-api
|
||||
liquid
|
||||
listen
|
||||
maxminddb
|
||||
mock_redis
|
||||
newrelic_rpm
|
||||
pg
|
||||
procore-sift
|
||||
pry-rails
|
||||
|
@ -713,7 +705,6 @@ DEPENDENCIES
|
|||
spring
|
||||
spring-watcher-listen
|
||||
squasher
|
||||
telegram-bot-ruby
|
||||
telephone_number
|
||||
time_diff
|
||||
twilio-ruby (~> 5.32.0)
|
||||
|
|
|
@ -22,6 +22,8 @@ ___
|
|||
<a title="Crowdin" target="_self" href="https://chatwoot.crowdin.com/chatwoot"><img src="https://badges.crowdin.net/e/37ced7eba411064bd792feb3b7a28b16/localized.svg"></a>
|
||||
<a href="https://discord.gg/cJXdrwS"><img src="https://img.shields.io/discord/647412545203994635" alt="Discord"></a>
|
||||
<a href="https://huntr.dev/bounties/disclose"><img src="https://cdn.huntr.dev/huntr_security_badge_mono.svg" alt="Huntr"></a>
|
||||
<a href="https://status.chatwoot.com"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fchatwoot%2Fstatus%2Fmaster%2Fapi%2Fchatwoot%2Fuptime.json" alt="uptime"></a>
|
||||
<a href="https://status.chatwoot.com"><img src="https://img.shields.io/endpoint?url=https%3A%2F%2Fraw.githubusercontent.com%2Fchatwoot%2Fstatus%2Fmaster%2Fapi%2Fchatwoot%2Fresponse-time.json" alt="response time"></a>
|
||||
</p>
|
||||
|
||||
<img src="https://s3.us-west-2.amazonaws.com/gh-assets.chatwoot.com/chatwoot-dashboard-assets.png" width="100%" alt="Chat dashboard"/>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
class ContactMergeAction
|
||||
include Events::Types
|
||||
pattr_initialize [:account!, :base_contact!, :mergee_contact!]
|
||||
|
||||
def perform
|
||||
|
@ -11,7 +12,7 @@ class ContactMergeAction
|
|||
merge_conversations
|
||||
merge_messages
|
||||
merge_contact_inboxes
|
||||
remove_mergee_contact
|
||||
merge_and_remove_mergee_contact
|
||||
end
|
||||
@base_contact
|
||||
end
|
||||
|
@ -40,7 +41,18 @@ class ContactMergeAction
|
|||
ContactInbox.where(contact_id: @mergee_contact.id).update(contact_id: @base_contact.id)
|
||||
end
|
||||
|
||||
def remove_mergee_contact
|
||||
def merge_and_remove_mergee_contact
|
||||
mergable_attribute_keys = %w[identifier name email phone_number custom_attributes]
|
||||
base_contact_attributes = base_contact.attributes.slice(*mergable_attribute_keys).compact_blank
|
||||
mergee_contact_attributes = mergee_contact.attributes.slice(*mergable_attribute_keys).compact_blank
|
||||
|
||||
# attributes in base contact are given preference
|
||||
merged_attributes = mergee_contact_attributes.deep_merge(base_contact_attributes)
|
||||
# retaining old pubsub token to notify the contacts that are listening
|
||||
mergee_pubsub_token = mergee_contact.pubsub_token
|
||||
|
||||
@mergee_contact.destroy!
|
||||
Rails.configuration.dispatcher.dispatch(CONTACT_MERGED, Time.zone.now, contact: @base_contact, tokens: [mergee_pubsub_token])
|
||||
@base_contact.update!(merged_attributes)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -43,7 +43,7 @@ $woot-logo-padding: $space-large $space-two;
|
|||
// Colors
|
||||
$color-woot: #1f93ff;
|
||||
$color-gray: #6e6f73;
|
||||
$color-light-gray: #999a9b;
|
||||
$color-light-gray: #747677;
|
||||
$color-border: #e0e6ed;
|
||||
$color-border-light: #f0f4f5;
|
||||
$color-background: #f4f6fb;
|
||||
|
|
|
@ -148,6 +148,14 @@ class Messages::Facebook::MessageBuilder
|
|||
}
|
||||
end
|
||||
|
||||
def process_contact_params_result(result)
|
||||
{
|
||||
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
|
||||
account_id: @inbox.account_id,
|
||||
remote_avatar_url: result['profile_pic'] || ''
|
||||
}
|
||||
end
|
||||
|
||||
def contact_params
|
||||
begin
|
||||
k = Koala::Facebook::API.new(@inbox.channel.page_access_token) if @inbox.facebook?
|
||||
|
@ -155,14 +163,15 @@ class Messages::Facebook::MessageBuilder
|
|||
rescue Koala::Facebook::AuthenticationError
|
||||
@inbox.channel.authorization_error!
|
||||
raise
|
||||
rescue Koala::Facebook::ClientError => e
|
||||
result = {}
|
||||
# OAuthException, code: 100, error_subcode: 2018218, message: (#100) No profile available for this user
|
||||
# We don't need to capture this error as we don't care about contact params in case of echo messages
|
||||
Sentry.capture_exception(e) unless outgoing_echo?
|
||||
rescue StandardError => e
|
||||
result = {}
|
||||
Sentry.capture_exception(e)
|
||||
end
|
||||
{
|
||||
name: "#{result['first_name'] || 'John'} #{result['last_name'] || 'Doe'}",
|
||||
account_id: @inbox.account_id,
|
||||
remote_avatar_url: result['profile_pic'] || ''
|
||||
}
|
||||
process_contact_params_result(result)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,21 +15,25 @@ class Messages::MessageBuilder
|
|||
|
||||
def perform
|
||||
@message = @conversation.messages.build(message_params)
|
||||
if @attachments.present?
|
||||
@attachments.each do |uploaded_attachment|
|
||||
attachment = @message.attachments.new(
|
||||
account_id: @message.account_id,
|
||||
file_type: file_type(uploaded_attachment&.content_type)
|
||||
)
|
||||
attachment.file.attach(uploaded_attachment)
|
||||
end
|
||||
end
|
||||
@message.save
|
||||
process_attachments
|
||||
@message.save!
|
||||
@message
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def process_attachments
|
||||
return if @attachments.blank?
|
||||
|
||||
@attachments.each do |uploaded_attachment|
|
||||
@message.attachments.build(
|
||||
account_id: @message.account_id,
|
||||
file_type: file_type(uploaded_attachment&.content_type),
|
||||
file: uploaded_attachment
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def message_type
|
||||
if @conversation.inbox.channel_type != 'Channel::Api' && @message_type == 'incoming'
|
||||
raise StandardError, 'Incoming messages are only allowed in Api inboxes'
|
||||
|
|
|
@ -4,7 +4,7 @@ class NotificationSubscriptionBuilder
|
|||
def perform
|
||||
# if multiple accounts were used to login in same browser
|
||||
move_subscription_to_user if identifier_subscription && identifier_subscription.user_id != user.id
|
||||
build_identifier_subscription if identifier_subscription.blank?
|
||||
identifier_subscription.blank? ? build_identifier_subscription : update_identifier_subscription
|
||||
identifier_subscription
|
||||
end
|
||||
|
||||
|
@ -25,6 +25,10 @@ class NotificationSubscriptionBuilder
|
|||
end
|
||||
|
||||
def build_identifier_subscription
|
||||
user.notification_subscriptions.create(params.merge(identifier: identifier))
|
||||
@identifier_subscription = user.notification_subscriptions.create(params.merge(identifier: identifier))
|
||||
end
|
||||
|
||||
def update_identifier_subscription
|
||||
identifier_subscription.update(params.merge(identifier: identifier))
|
||||
end
|
||||
end
|
||||
|
|
|
@ -32,9 +32,16 @@ class V2::ReportBuilder
|
|||
private
|
||||
|
||||
def scope
|
||||
return account if params[:type].match?('account')
|
||||
return inbox if params[:type].match?('inbox')
|
||||
return user if params[:type].match?('agent')
|
||||
case params[:type]
|
||||
when :account
|
||||
account
|
||||
when :inbox
|
||||
inbox
|
||||
when :agent
|
||||
user
|
||||
when :label
|
||||
label
|
||||
end
|
||||
end
|
||||
|
||||
def inbox
|
||||
|
@ -45,6 +52,10 @@ class V2::ReportBuilder
|
|||
@user ||= account.users.where(id: params[:id]).first
|
||||
end
|
||||
|
||||
def label
|
||||
@label ||= account.labels.where(id: params[:id]).first
|
||||
end
|
||||
|
||||
def conversations_count
|
||||
scope.conversations
|
||||
.group_by_day(:created_at, range: range, default_value: 0)
|
||||
|
|
|
@ -79,6 +79,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
|||
@resolved_contacts = Current.account.contacts
|
||||
.where.not(email: [nil, ''])
|
||||
.or(Current.account.contacts.where.not(phone_number: [nil, '']))
|
||||
.or(Current.account.contacts.where.not(identifier: [nil, '']))
|
||||
@resolved_contacts = @resolved_contacts.tagged_with(params[:labels], any: true) if params[:labels].present?
|
||||
@resolved_contacts
|
||||
end
|
||||
|
|
|
@ -1,20 +1,34 @@
|
|||
class Api::V1::Accounts::Conversations::AssignmentsController < Api::V1::Accounts::Conversations::BaseController
|
||||
# assigns agent/team to a conversation
|
||||
def create
|
||||
set_assignee
|
||||
render json: @assignee
|
||||
if params.key?(:assignee_id)
|
||||
set_agent
|
||||
elsif params.key?(:team_id)
|
||||
set_team
|
||||
else
|
||||
render json: nil
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_assignee
|
||||
# if params[:assignee_id] is not a valid id, it will set to nil, hence unassigning the conversation
|
||||
if params.key?(:assignee_id)
|
||||
@assignee = Current.account.users.find_by(id: params[:assignee_id])
|
||||
@conversation.update_assignee(@assignee)
|
||||
elsif params.key?(:team_id)
|
||||
@assignee = Current.account.teams.find_by(id: params[:team_id])
|
||||
@conversation.update!(team: @assignee)
|
||||
def set_agent
|
||||
@agent = Current.account.users.find_by(id: params[:assignee_id])
|
||||
@conversation.update_assignee(@agent)
|
||||
render_agent
|
||||
end
|
||||
|
||||
def render_agent
|
||||
if @agent.nil?
|
||||
render json: nil
|
||||
else
|
||||
render partial: 'api/v1/models/agent', formats: [:json], locals: { resource: @agent }
|
||||
end
|
||||
end
|
||||
|
||||
def set_team
|
||||
@team = Current.account.teams.find_by(id: params[:team_id])
|
||||
@conversation.update!(team: @team)
|
||||
render json: @team
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,12 +31,13 @@ class Api::V1::Accounts::CustomAttributeDefinitionsController < Api::V1::Account
|
|||
end
|
||||
|
||||
def fetch_custom_attribute_definition
|
||||
@custom_attribute_definition = @custom_attribute_definitions.find(permitted_params[:id])
|
||||
@custom_attribute_definition = Current.account.custom_attribute_definitions.find(permitted_params[:id])
|
||||
end
|
||||
|
||||
def permitted_payload
|
||||
params.require(:custom_attribute_definition).permit(
|
||||
:attribute_display_name,
|
||||
:attribute_description,
|
||||
:attribute_display_type,
|
||||
:attribute_key,
|
||||
:attribute_model,
|
||||
|
@ -45,6 +46,6 @@ class Api::V1::Accounts::CustomAttributeDefinitionsController < Api::V1::Account
|
|||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:id, :filter_type)
|
||||
params.permit(:id, :filter_type, :attribute_model)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,26 +1,40 @@
|
|||
class Api::V1::Accounts::InboxMembersController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_inbox, only: [:create, :show]
|
||||
before_action :current_agents_ids, only: [:create]
|
||||
before_action :fetch_inbox
|
||||
before_action :current_agents_ids, only: [:update]
|
||||
|
||||
def create
|
||||
authorize @inbox, :create?
|
||||
begin
|
||||
# update also done via same action
|
||||
update_agents_list
|
||||
head :ok
|
||||
rescue StandardError => e
|
||||
Rails.logger.debug { "Rescued: #{e.inspect}" }
|
||||
render_could_not_create_error('Could not add agents to inbox')
|
||||
ActiveRecord::Base.transaction do
|
||||
params[:user_ids].map { |user_id| @inbox.add_member(user_id) }
|
||||
end
|
||||
fetch_updated_agents
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @inbox, :show?
|
||||
@agents = Current.account.users.where(id: @inbox.members.select(:user_id))
|
||||
fetch_updated_agents
|
||||
end
|
||||
|
||||
def update
|
||||
authorize @inbox, :update?
|
||||
update_agents_list
|
||||
fetch_updated_agents
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @inbox, :destroy?
|
||||
ActiveRecord::Base.transaction do
|
||||
params[:user_ids].map { |user_id| @inbox.remove_member(user_id) }
|
||||
end
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_updated_agents
|
||||
@agents = Current.account.users.where(id: @inbox.members.select(:user_id))
|
||||
end
|
||||
|
||||
def update_agents_list
|
||||
# get all the user_ids which the inbox currently has as members.
|
||||
# get the list of user_ids from params
|
||||
|
|
|
@ -1,12 +1,15 @@
|
|||
class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_inbox, except: [:index, :create]
|
||||
before_action :fetch_agent_bot, only: [:set_agent_bot]
|
||||
before_action :check_authorization
|
||||
# we are already handling the authorization in fetch inbox
|
||||
before_action :check_authorization, except: [:show]
|
||||
|
||||
def index
|
||||
@inboxes = policy_scope(Current.account.inboxes.order_by_name.includes(:channel, { avatar_attachment: [:blob] }))
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
def assignable_agents
|
||||
@assignable_agents = (Current.account.users.where(id: @inbox.members.select(:user_id)) + Current.account.administrators).uniq
|
||||
end
|
||||
|
@ -15,26 +18,32 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
|||
@campaigns = @inbox.campaigns
|
||||
end
|
||||
|
||||
def avatar
|
||||
@inbox.avatar.attachment.destroy! if @inbox.avatar.attached?
|
||||
head :ok
|
||||
end
|
||||
|
||||
def create
|
||||
ActiveRecord::Base.transaction do
|
||||
channel = create_channel
|
||||
@inbox = Current.account.inboxes.build(
|
||||
name: permitted_params[:name],
|
||||
greeting_message: permitted_params[:greeting_message],
|
||||
greeting_enabled: permitted_params[:greeting_enabled],
|
||||
channel: channel
|
||||
{
|
||||
name: inbox_name(channel),
|
||||
channel: channel
|
||||
}.merge(
|
||||
permitted_params.except(:channel)
|
||||
)
|
||||
)
|
||||
@inbox.avatar.attach(permitted_params[:avatar])
|
||||
@inbox.save!
|
||||
end
|
||||
end
|
||||
|
||||
def update
|
||||
@inbox.update(inbox_update_params.except(:channel))
|
||||
@inbox.update(permitted_params.except(:channel))
|
||||
@inbox.update_working_hours(params.permit(working_hours: Inbox::OFFISABLE_ATTRS)[:working_hours]) if params[:working_hours]
|
||||
return unless @inbox.channel.is_a?(Channel::WebWidget) && inbox_update_params[:channel].present?
|
||||
|
||||
@inbox.channel.update!(inbox_update_params[:channel])
|
||||
channel_attributes = get_channel_attributes(@inbox.channel_type)
|
||||
@inbox.channel.update!(permitted_params(channel_attributes)[:channel]) if permitted_params(channel_attributes)[:channel].present?
|
||||
update_channel_feature_flags
|
||||
end
|
||||
|
||||
|
@ -69,43 +78,57 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
|
|||
@agent_bot = AgentBot.find(params[:agent_bot]) if params[:agent_bot]
|
||||
end
|
||||
|
||||
def inbox_name(channel)
|
||||
return channel.try(:bot_name) if channel.is_a?(Channel::Telegram)
|
||||
|
||||
permitted_params[:name]
|
||||
end
|
||||
|
||||
def create_channel
|
||||
case permitted_params[:channel][:type]
|
||||
when 'web_widget'
|
||||
Current.account.web_widgets.create!(permitted_params[:channel].except(:type))
|
||||
Current.account.web_widgets.create!(permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel].except(:type))
|
||||
when 'api'
|
||||
Current.account.api_channels.create!(permitted_params[:channel].except(:type))
|
||||
Current.account.api_channels.create!(permitted_params(Channel::Api::EDITABLE_ATTRS)[:channel].except(:type))
|
||||
when 'email'
|
||||
Current.account.email_channels.create!(permitted_params[:channel].except(:type))
|
||||
Current.account.email_channels.create!(permitted_params(Channel::Email::EDITABLE_ATTRS)[:channel].except(:type))
|
||||
when 'line'
|
||||
Current.account.line_channels.create!(permitted_params(Channel::Line::EDITABLE_ATTRS)[:channel].except(:type))
|
||||
when 'telegram'
|
||||
Current.account.telegram_channels.create!(permitted_params(Channel::Telegram::EDITABLE_ATTRS)[:channel].except(:type))
|
||||
end
|
||||
end
|
||||
|
||||
def update_channel_feature_flags
|
||||
return unless inbox_update_params[:channel].key? :selected_feature_flags
|
||||
return unless @inbox.web_widget?
|
||||
return unless permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel].key? :selected_feature_flags
|
||||
|
||||
@inbox.channel.selected_feature_flags = inbox_update_params[:channel][:selected_feature_flags]
|
||||
@inbox.channel.selected_feature_flags = permitted_params(Channel::WebWidget::EDITABLE_ATTRS)[:channel][:selected_feature_flags]
|
||||
@inbox.channel.save!
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:id, :avatar, :name, :greeting_message, :greeting_enabled, :enable_email_collect, :csat_survey_enabled, channel:
|
||||
[:type, :website_url, :widget_color, :welcome_title, :welcome_tagline, :webhook_url, :email, :reply_time])
|
||||
def permitted_params(channel_attributes = [])
|
||||
params.permit(
|
||||
:name, :avatar, :greeting_enabled, :greeting_message, :enable_email_collect, :csat_survey_enabled,
|
||||
:enable_auto_assignment, :working_hours_enabled, :out_of_office_message, :timezone,
|
||||
channel: [:type, *channel_attributes]
|
||||
)
|
||||
end
|
||||
|
||||
def inbox_update_params
|
||||
params.permit(:enable_auto_assignment, :enable_email_collect, :name, :avatar, :greeting_message, :greeting_enabled, :csat_survey_enabled,
|
||||
:working_hours_enabled, :out_of_office_message, :timezone,
|
||||
channel: [
|
||||
:website_url,
|
||||
:widget_color,
|
||||
:welcome_title,
|
||||
:welcome_tagline,
|
||||
:webhook_url,
|
||||
:email,
|
||||
:reply_time,
|
||||
:pre_chat_form_enabled,
|
||||
{ pre_chat_form_options: [:pre_chat_message, :require_email] },
|
||||
{ selected_feature_flags: [] }
|
||||
])
|
||||
def get_channel_attributes(channel_type)
|
||||
case channel_type
|
||||
when 'Channel::WebWidget'
|
||||
Channel::WebWidget::EDITABLE_ATTRS
|
||||
when 'Channel::Api'
|
||||
Channel::Api::EDITABLE_ATTRS
|
||||
when 'Channel::Email'
|
||||
Channel::Email::EDITABLE_ATTRS
|
||||
when 'Channel::Telegram'
|
||||
Channel::Telegram::EDITABLE_ATTRS
|
||||
when 'Channel::Line'
|
||||
Channel::Line::EDITABLE_ATTRS
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,8 +8,8 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
|||
|
||||
def create
|
||||
@message = conversation.messages.new(message_params)
|
||||
@message.save
|
||||
build_attachment
|
||||
@message.save!
|
||||
end
|
||||
|
||||
def update
|
||||
|
@ -29,13 +29,12 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
|||
return if params[:message][:attachments].blank?
|
||||
|
||||
params[:message][:attachments].each do |uploaded_attachment|
|
||||
attachment = @message.attachments.new(
|
||||
@message.attachments.new(
|
||||
account_id: @message.account_id,
|
||||
file_type: helpers.file_type(uploaded_attachment&.content_type)
|
||||
file_type: helpers.file_type(uploaded_attachment&.content_type),
|
||||
file: uploaded_attachment
|
||||
)
|
||||
attachment.file.attach(uploaded_attachment)
|
||||
end
|
||||
@message.save!
|
||||
end
|
||||
|
||||
def set_conversation
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
||||
before_action :check_authorization
|
||||
|
||||
def account
|
||||
builder = V2::ReportBuilder.new(Current.account, account_report_params)
|
||||
def index
|
||||
builder = V2::ReportBuilder.new(Current.account, report_params)
|
||||
data = builder.build
|
||||
render json: data
|
||||
end
|
||||
|
||||
def account_summary
|
||||
render json: account_summary_metrics
|
||||
def summary
|
||||
render json: summary_metrics
|
||||
end
|
||||
|
||||
def agents
|
||||
|
@ -23,31 +23,39 @@ class Api::V2::Accounts::ReportsController < Api::V1::Accounts::BaseController
|
|||
render layout: false, template: 'api/v2/accounts/reports/inboxes.csv.erb', format: 'csv'
|
||||
end
|
||||
|
||||
def labels
|
||||
response.headers['Content-Type'] = 'text/csv'
|
||||
response.headers['Content-Disposition'] = 'attachment; filename=labels_report.csv'
|
||||
render layout: false, template: 'api/v2/accounts/reports/labels.csv.erb', format: 'csv'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
raise Pundit::NotAuthorizedError unless Current.account_user.administrator?
|
||||
end
|
||||
|
||||
def account_summary_params
|
||||
def summary_params
|
||||
{
|
||||
type: :account,
|
||||
type: params[:type].to_sym,
|
||||
since: params[:since],
|
||||
until: params[:until]
|
||||
until: params[:until],
|
||||
id: params[:id]
|
||||
}
|
||||
end
|
||||
|
||||
def account_report_params
|
||||
def report_params
|
||||
{
|
||||
metric: params[:metric],
|
||||
type: :account,
|
||||
type: params[:type].to_sym,
|
||||
since: params[:since],
|
||||
until: params[:until]
|
||||
until: params[:until],
|
||||
id: params[:id]
|
||||
}
|
||||
end
|
||||
|
||||
def account_summary_metrics
|
||||
builder = V2::ReportBuilder.new(Current.account, account_summary_params)
|
||||
def summary_metrics
|
||||
builder = V2::ReportBuilder.new(Current.account, summary_params)
|
||||
builder.summary
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
class ApplicationController < ActionController::Base
|
||||
include DeviseTokenAuth::Concerns::SetUserByToken
|
||||
include RequestExceptionHandler
|
||||
include Pundit
|
||||
include SwitchLocale
|
||||
|
||||
|
@ -9,22 +10,8 @@ class ApplicationController < ActionController::Base
|
|||
around_action :switch_locale
|
||||
around_action :handle_with_exception, unless: :devise_controller?
|
||||
|
||||
rescue_from ActiveRecord::RecordInvalid, with: :render_record_invalid
|
||||
|
||||
private
|
||||
|
||||
def handle_with_exception
|
||||
yield
|
||||
rescue ActiveRecord::RecordNotFound => e
|
||||
Sentry.capture_exception(e)
|
||||
render_not_found_error('Resource could not be found')
|
||||
rescue Pundit::NotAuthorizedError
|
||||
render_unauthorized('You are not authorized to do this action')
|
||||
ensure
|
||||
# to address the thread variable leak issues in Puma/Thin webserver
|
||||
Current.reset
|
||||
end
|
||||
|
||||
def set_current_user
|
||||
@user ||= current_user
|
||||
Current.user = @user
|
||||
|
@ -34,32 +21,6 @@ class ApplicationController < ActionController::Base
|
|||
@subscription ||= Current.account.subscription
|
||||
end
|
||||
|
||||
def render_unauthorized(message)
|
||||
render json: { error: message }, status: :unauthorized
|
||||
end
|
||||
|
||||
def render_not_found_error(message)
|
||||
render json: { error: message }, status: :not_found
|
||||
end
|
||||
|
||||
def render_could_not_create_error(message)
|
||||
render json: { error: message }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def render_internal_server_error(message)
|
||||
render json: { error: message }, status: :internal_server_error
|
||||
end
|
||||
|
||||
def render_record_invalid(exception)
|
||||
render json: {
|
||||
message: exception.record.errors.full_messages.join(', ')
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def render_error_response(exception)
|
||||
render json: exception.to_hash, status: exception.http_status
|
||||
end
|
||||
|
||||
def pundit_user
|
||||
{
|
||||
user: Current.user,
|
||||
|
|
47
app/controllers/concerns/request_exception_handler.rb
Normal file
47
app/controllers/concerns/request_exception_handler.rb
Normal file
|
@ -0,0 +1,47 @@
|
|||
module RequestExceptionHandler
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
rescue_from ActiveRecord::RecordInvalid, with: :render_record_invalid
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def handle_with_exception
|
||||
yield
|
||||
rescue ActiveRecord::RecordNotFound => e
|
||||
Sentry.capture_exception(e)
|
||||
render_not_found_error('Resource could not be found')
|
||||
rescue Pundit::NotAuthorizedError
|
||||
render_unauthorized('You are not authorized to do this action')
|
||||
ensure
|
||||
# to address the thread variable leak issues in Puma/Thin webserver
|
||||
Current.reset
|
||||
end
|
||||
|
||||
def render_unauthorized(message)
|
||||
render json: { error: message }, status: :unauthorized
|
||||
end
|
||||
|
||||
def render_not_found_error(message)
|
||||
render json: { error: message }, status: :not_found
|
||||
end
|
||||
|
||||
def render_could_not_create_error(message)
|
||||
render json: { error: message }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def render_internal_server_error(message)
|
||||
render json: { error: message }, status: :internal_server_error
|
||||
end
|
||||
|
||||
def render_record_invalid(exception)
|
||||
render json: {
|
||||
message: exception.record.errors.full_messages.join(', ')
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def render_error_response(exception)
|
||||
render json: exception.to_hash, status: exception.http_status
|
||||
end
|
||||
end
|
|
@ -23,7 +23,9 @@ class DashboardController < ActionController::Base
|
|||
'CREATE_NEW_ACCOUNT_FROM_DASHBOARD',
|
||||
'CHATWOOT_INBOX_TOKEN',
|
||||
'API_CHANNEL_NAME',
|
||||
'API_CHANNEL_THUMBNAIL'
|
||||
'API_CHANNEL_THUMBNAIL',
|
||||
'ANALYTICS_TOKEN',
|
||||
'ANALYTICS_HOST'
|
||||
).merge(
|
||||
APP_VERSION: Chatwoot.config[:version]
|
||||
)
|
||||
|
|
|
@ -16,7 +16,7 @@ class Platform::Api::V1::AccountsController < PlatformController
|
|||
end
|
||||
|
||||
def destroy
|
||||
# TODO: obfusicate account
|
||||
DeleteObjectJob.perform_later(@resource)
|
||||
head :ok
|
||||
end
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@ class Platform::Api::V1::UsersController < PlatformController
|
|||
|
||||
def create
|
||||
@resource = (User.find_by(email: user_params[:email]) || User.new(user_params))
|
||||
@resource.confirm
|
||||
@resource.save!
|
||||
@resource.confirm
|
||||
@platform_app.platform_app_permissibles.find_or_create_by(permissible: @resource)
|
||||
end
|
||||
|
||||
|
@ -19,21 +19,33 @@ class Platform::Api::V1::UsersController < PlatformController
|
|||
def show; end
|
||||
|
||||
def update
|
||||
@resource.update!(user_params)
|
||||
@resource.assign_attributes(user_update_params)
|
||||
@resource.save!
|
||||
end
|
||||
|
||||
def destroy
|
||||
# TODO: obfusicate user
|
||||
DeleteObjectJob.perform_later(@resource)
|
||||
head :ok
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def user_custom_attributes
|
||||
return @resource.custom_attributes.merge(user_params[:custom_attributes]) if user_params[:custom_attributes]
|
||||
|
||||
@resource.custom_attributes
|
||||
end
|
||||
|
||||
def user_update_params
|
||||
# we want the merged custom attributes not the original one
|
||||
user_params.except(:custom_attributes).merge({ custom_attributes: user_custom_attributes })
|
||||
end
|
||||
|
||||
def set_resource
|
||||
@resource = User.find(params[:id])
|
||||
end
|
||||
|
||||
def user_params
|
||||
params.permit(:name, :email, :password)
|
||||
params.permit(:name, :email, :password, custom_attributes: {})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
class PlatformController < ActionController::API
|
||||
include RequestExceptionHandler
|
||||
|
||||
before_action :ensure_access_token
|
||||
before_action :set_platform_app
|
||||
before_action :set_resource, only: [:update, :show, :destroy]
|
||||
|
|
|
@ -7,8 +7,8 @@ class Public::Api::V1::Inboxes::MessagesController < Public::Api::V1::InboxesCon
|
|||
|
||||
def create
|
||||
@message = @conversation.messages.new(message_params)
|
||||
@message.save
|
||||
build_attachment
|
||||
@message.save!
|
||||
end
|
||||
|
||||
def update
|
||||
|
@ -23,13 +23,12 @@ class Public::Api::V1::Inboxes::MessagesController < Public::Api::V1::InboxesCon
|
|||
return if params[:attachments].blank?
|
||||
|
||||
params[:attachments].each do |uploaded_attachment|
|
||||
attachment = @message.attachments.new(
|
||||
@message.attachments.new(
|
||||
account_id: @message.account_id,
|
||||
file_type: helpers.file_type(uploaded_attachment&.content_type)
|
||||
file_type: helpers.file_type(uploaded_attachment&.content_type),
|
||||
file: uploaded_attachment
|
||||
)
|
||||
attachment.file.attach(uploaded_attachment)
|
||||
end
|
||||
@message.save!
|
||||
end
|
||||
|
||||
def message_finder_params
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
# TODO: we should switch to ActionController::API for the base classes
|
||||
# One of the specs is failing when I tried doing that, lets revisit in future
|
||||
class PublicController < ActionController::Base
|
||||
include RequestExceptionHandler
|
||||
skip_before_action :verify_authenticity_token
|
||||
end
|
||||
|
|
|
@ -3,10 +3,9 @@ class SuperAdmin::DashboardController < SuperAdmin::ApplicationController
|
|||
|
||||
def index
|
||||
@data = Conversation.unscoped.group_by_day(:created_at, range: 30.days.ago..2.seconds.ago).count.to_a
|
||||
@accounts_count = number_with_delimiter(Account.all.length)
|
||||
@users_count = number_with_delimiter(User.all.length)
|
||||
@inboxes_count = number_with_delimiter(Inbox.all.length)
|
||||
@conversations_count = number_with_delimiter(Conversation.all.length)
|
||||
@messages_count = number_with_delimiter(Message.all.length)
|
||||
@accounts_count = number_with_delimiter(Account.count)
|
||||
@users_count = number_with_delimiter(User.count)
|
||||
@inboxes_count = number_with_delimiter(Inbox.count)
|
||||
@conversations_count = number_with_delimiter(Conversation.count)
|
||||
end
|
||||
end
|
||||
|
|
6
app/controllers/webhooks/line_controller.rb
Normal file
6
app/controllers/webhooks/line_controller.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
class Webhooks::LineController < ActionController::API
|
||||
def process_payload
|
||||
Webhooks::LineEventsJob.perform_later(params: params.to_unsafe_hash, signature: request.headers['x-line-signature'], post_body: request.raw_post)
|
||||
head :ok
|
||||
end
|
||||
end
|
6
app/controllers/webhooks/telegram_controller.rb
Normal file
6
app/controllers/webhooks/telegram_controller.rb
Normal file
|
@ -0,0 +1,6 @@
|
|||
class Webhooks::TelegramController < ActionController::API
|
||||
def process_payload
|
||||
Webhooks::TelegramEventsJob.perform_later(params.to_unsafe_hash)
|
||||
head :ok
|
||||
end
|
||||
end
|
12
app/drops/message_drop.rb
Normal file
12
app/drops/message_drop.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
class MessageDrop < BaseDrop
|
||||
include MessageFormatHelper
|
||||
|
||||
def sender_display_name
|
||||
@obj.sender.try(:available_name)
|
||||
end
|
||||
|
||||
def text_content
|
||||
content = @obj.try(:content)
|
||||
transform_user_mention_content content
|
||||
end
|
||||
end
|
|
@ -3,12 +3,12 @@ module FileTypeHelper
|
|||
return :image if [
|
||||
'image/jpeg',
|
||||
'image/png',
|
||||
'image/svg+xml',
|
||||
'image/gif',
|
||||
'image/tiff',
|
||||
'image/bmp'
|
||||
].include?(content_type)
|
||||
|
||||
return :video if content_type.include?('video/')
|
||||
return :audio if content_type.include?('audio/')
|
||||
|
||||
:file
|
||||
|
|
14
app/javascript/dashboard/api/attributes.js
Normal file
14
app/javascript/dashboard/api/attributes.js
Normal file
|
@ -0,0 +1,14 @@
|
|||
/* global axios */
|
||||
import ApiClient from './ApiClient';
|
||||
|
||||
class AttributeAPI extends ApiClient {
|
||||
constructor() {
|
||||
super('custom_attribute_definitions', { accountScoped: true });
|
||||
}
|
||||
|
||||
getAttributesByModel(modelId) {
|
||||
return axios.get(`${this.url}?attribute_model=${modelId}`);
|
||||
}
|
||||
}
|
||||
|
||||
export default new AttributeAPI();
|
|
@ -6,8 +6,8 @@ class InboxMembers extends ApiClient {
|
|||
super('inbox_members', { accountScoped: true });
|
||||
}
|
||||
|
||||
create({ inboxId, agentList }) {
|
||||
return axios.post(this.url, {
|
||||
update({ inboxId, agentList }) {
|
||||
return axios.patch(this.url, {
|
||||
inbox_id: inboxId,
|
||||
user_ids: agentList,
|
||||
});
|
||||
|
|
|
@ -13,6 +13,10 @@ class Inboxes extends ApiClient {
|
|||
getCampaigns(inboxId) {
|
||||
return axios.get(`${this.url}/${inboxId}/campaigns`);
|
||||
}
|
||||
|
||||
deleteInboxAvatar(inboxId) {
|
||||
return axios.delete(`${this.url}/${inboxId}/avatar`);
|
||||
}
|
||||
}
|
||||
|
||||
export default new Inboxes();
|
||||
|
|
|
@ -7,14 +7,14 @@ class ReportsAPI extends ApiClient {
|
|||
}
|
||||
|
||||
getAccountReports(metric, since, until) {
|
||||
return axios.get(`${this.url}/account`, {
|
||||
params: { metric, since, until },
|
||||
return axios.get(`${this.url}`, {
|
||||
params: { metric, since, until, type: 'account' },
|
||||
});
|
||||
}
|
||||
|
||||
getAccountSummary(since, until) {
|
||||
return axios.get(`${this.url}/account_summary`, {
|
||||
params: { since, until },
|
||||
return axios.get(`${this.url}/summary`, {
|
||||
params: { since, until, type: 'account' },
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -27,5 +27,12 @@ describe('#InboxesAPI', () => {
|
|||
'/api/v1/inboxes/2/campaigns'
|
||||
);
|
||||
});
|
||||
|
||||
it('#deleteInboxAvatar', () => {
|
||||
inboxesAPI.deleteInboxAvatar(2);
|
||||
expect(context.axiosMock.delete).toHaveBeenCalledWith(
|
||||
'/api/v1/inboxes/2/avatar'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -23,12 +23,13 @@ describe('#Reports API', () => {
|
|||
1621621800
|
||||
);
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v2/reports/account',
|
||||
'/api/v2/reports',
|
||||
{
|
||||
params: {
|
||||
metric: 'conversations_count',
|
||||
since: 1621103400,
|
||||
until: 1621621800,
|
||||
type: 'account'
|
||||
},
|
||||
}
|
||||
);
|
||||
|
@ -37,11 +38,12 @@ describe('#Reports API', () => {
|
|||
it('#getAccountSummary', () => {
|
||||
reportsAPI.getAccountSummary(1621103400, 1621621800);
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith(
|
||||
'/api/v2/reports/account_summary',
|
||||
'/api/v2/reports/summary',
|
||||
{
|
||||
params: {
|
||||
since: 1621103400,
|
||||
until: 1621621800,
|
||||
type: 'account'
|
||||
},
|
||||
}
|
||||
);
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
@import 'shared/assets/stylesheets/font-weights';
|
||||
@import 'shared/assets/stylesheets/shadows';
|
||||
@import 'shared/assets/stylesheets/border-radius';
|
||||
@import 'shared/assets/stylesheets/z-index';
|
||||
|
||||
@import 'variables';
|
||||
|
||||
|
|
|
@ -71,7 +71,6 @@
|
|||
}
|
||||
|
||||
&.over {
|
||||
|
||||
&::after {
|
||||
background: $color-woot;
|
||||
}
|
||||
|
@ -80,7 +79,7 @@
|
|||
background: $color-woot;
|
||||
}
|
||||
|
||||
&+.item {
|
||||
& + .item {
|
||||
&::before {
|
||||
background: $color-woot;
|
||||
}
|
||||
|
@ -132,10 +131,13 @@
|
|||
@include padding($space-medium);
|
||||
@include border-light;
|
||||
@include full-height();
|
||||
|
||||
&.height-auto {
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.inoboxes-list {
|
||||
|
||||
.inbox-item {
|
||||
@include margin($space-normal);
|
||||
@include flex;
|
||||
|
@ -189,9 +191,9 @@
|
|||
align-self: center;
|
||||
color: $medium-gray;
|
||||
font-size: $font-size-small;
|
||||
opacity: .7;
|
||||
opacity: 0.7;
|
||||
transform: translateX(0);
|
||||
transition: opacity 0.100s ease-in 0s, transform 0.200s ease-in 0.030s;
|
||||
transition: opacity 0.1s ease-in 0s, transform 0.2s ease-in 0.03s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -226,7 +228,7 @@
|
|||
@include padding($space-medium);
|
||||
}
|
||||
|
||||
>a>img {
|
||||
> a > img {
|
||||
width: $space-larger * 5;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -144,7 +144,10 @@
|
|||
padding-left: 0;
|
||||
|
||||
.conversation--details {
|
||||
border-radius: var(--border-radius-small);
|
||||
margin-left: 0;
|
||||
padding-left: var(--space-two);
|
||||
padding-right: var(--space-small);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -113,10 +113,18 @@
|
|||
}
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: $space-small;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
&.unread--toast {
|
||||
+.right {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
+.left {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
span {
|
||||
@include elegant-card;
|
||||
@include round-corner;
|
||||
|
@ -140,6 +148,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
&.left {
|
||||
|
||||
.bubble {
|
||||
|
@ -183,6 +192,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
+.unread--toast {
|
||||
+.right {
|
||||
margin-top: $space-one;
|
||||
|
||||
.bubble {
|
||||
border-top-right-radius: $space-one;
|
||||
}
|
||||
}
|
||||
|
||||
+.left {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.right {
|
||||
|
@ -226,6 +248,21 @@
|
|||
border-top-left-radius: $space-one;
|
||||
}
|
||||
}
|
||||
|
||||
+.unread--toast {
|
||||
+.left {
|
||||
margin-top: $space-one;
|
||||
|
||||
.bubble {
|
||||
border-top-left-radius: $space-one;
|
||||
}
|
||||
}
|
||||
|
||||
+.right {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.center {
|
||||
|
@ -259,11 +296,11 @@
|
|||
display: flex;
|
||||
font-size: var(--font-size-small);
|
||||
justify-content: center;
|
||||
margin: var(--space-small) var(--space-normal);
|
||||
padding: var(--space-small) var(--space-normal);
|
||||
margin: var(--space-smaller) 0;
|
||||
padding: var(--space-smaller) var(--space-micro) var(--space-smaller) var(--space-one);
|
||||
|
||||
.is-text {
|
||||
display: inline-block;
|
||||
display: inline-flex;
|
||||
text-align: start;
|
||||
|
||||
@include breakpoint(xxxlarge up) {
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
import { action } from '@storybook/addon-actions';
|
||||
import AccordionItemComponent from './AccordionItem';
|
||||
|
||||
export default {
|
||||
title: 'Components/Generic/Accordion',
|
||||
component: AccordionItemComponent,
|
||||
argTypes: {
|
||||
title: {
|
||||
control: {
|
||||
type: 'string',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { AccordionItem: AccordionItemComponent },
|
||||
template: `
|
||||
<accordion-item v-bind="$props" @click="onClick">
|
||||
This is a sample content you can pass as a slot
|
||||
</accordion-item>
|
||||
`,
|
||||
});
|
||||
|
||||
export const AccordionItem = Template.bind({});
|
||||
AccordionItem.args = {
|
||||
onClick: action('Added'),
|
||||
title: 'Title of the accordion item',
|
||||
};
|
110
app/javascript/dashboard/components/Accordion/AccordionItem.vue
Normal file
110
app/javascript/dashboard/components/Accordion/AccordionItem.vue
Normal file
|
@ -0,0 +1,110 @@
|
|||
<template>
|
||||
<div class="cw-accordion">
|
||||
<button class="cw-accordion--title" @click="$emit('click')">
|
||||
<div class="cw-accordion--title-wrap">
|
||||
<emoji-or-icon class="icon-or-emoji" :icon="icon" :emoji="emoji" />
|
||||
<h5>
|
||||
{{ title }}
|
||||
</h5>
|
||||
</div>
|
||||
<div class="button-icon--wrap">
|
||||
<slot name="button" />
|
||||
<div class="chevron-icon__wrap">
|
||||
<i v-if="isOpen" class="ion-minus chevron-icon"></i>
|
||||
<i v-else class="ion-plus chevron-icon"></i>
|
||||
</div>
|
||||
</div>
|
||||
</button>
|
||||
<div v-if="isOpen" class="cw-accordion--content">
|
||||
<slot />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EmojiOrIcon from 'shared/components/EmojiOrIcon';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
EmojiOrIcon,
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
emoji: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isOpen: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.cw-accordion {
|
||||
// This is done to fix contact sidebar border issues
|
||||
// If you are using it else, find a fix to remove this hack
|
||||
margin-top: -1px;
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
.cw-accordion--title {
|
||||
align-items: center;
|
||||
background: var(--b-50);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
border-top: 1px solid var(--color-border);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin: 0;
|
||||
padding: var(--space-small) var(--space-normal);
|
||||
user-select: none;
|
||||
width: 100%;
|
||||
|
||||
h5 {
|
||||
font-size: var(--font-size-normal);
|
||||
margin-bottom: 0;
|
||||
padding: 0 var(--space-small) 0 0;
|
||||
}
|
||||
}
|
||||
|
||||
.cw-accordion--title-wrap {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--space-micro);
|
||||
}
|
||||
|
||||
.title-icon__wrap {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.icon-or-emoji {
|
||||
display: inline-block;
|
||||
width: var(--space-two);
|
||||
}
|
||||
|
||||
.button-icon--wrap {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.chevron-icon__wrap {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: var(--space-slab);
|
||||
color: var(--w-500);
|
||||
}
|
||||
|
||||
.cw-accordion--content {
|
||||
padding: var(--space-normal);
|
||||
}
|
||||
</style>
|
|
@ -194,7 +194,7 @@ export default {
|
|||
});
|
||||
},
|
||||
methods: {
|
||||
handleKeyEvents(e) {
|
||||
getKeyboardListenerParams() {
|
||||
const allConversations = this.$refs.activeConversation.querySelectorAll(
|
||||
'div.conversations-list div.conversation'
|
||||
);
|
||||
|
@ -205,7 +205,19 @@ export default {
|
|||
activeConversation
|
||||
);
|
||||
const lastConversationIndex = allConversations.length - 1;
|
||||
return {
|
||||
allConversations,
|
||||
activeConversation,
|
||||
activeConversationIndex,
|
||||
lastConversationIndex,
|
||||
};
|
||||
},
|
||||
handleKeyEvents(e) {
|
||||
if (hasPressedAltAndJKey(e)) {
|
||||
const {
|
||||
allConversations,
|
||||
activeConversationIndex,
|
||||
} = this.getKeyboardListenerParams();
|
||||
if (activeConversationIndex === -1) {
|
||||
allConversations[0].click();
|
||||
}
|
||||
|
@ -214,6 +226,11 @@ export default {
|
|||
}
|
||||
}
|
||||
if (hasPressedAltAndKKey(e)) {
|
||||
const {
|
||||
allConversations,
|
||||
activeConversationIndex,
|
||||
lastConversationIndex,
|
||||
} = this.getKeyboardListenerParams();
|
||||
if (activeConversationIndex === -1) {
|
||||
allConversations[lastConversationIndex].click();
|
||||
} else if (activeConversationIndex < lastConversationIndex) {
|
||||
|
|
|
@ -54,7 +54,7 @@ export default {
|
|||
},
|
||||
mounted() {
|
||||
document.addEventListener('keydown', e => {
|
||||
if (this.show && e.keyCode === 27) {
|
||||
if (this.show && e.code === 'Escape') {
|
||||
this.onClose();
|
||||
}
|
||||
});
|
||||
|
|
|
@ -176,7 +176,9 @@ export default {
|
|||
'.conversations-list .conversation'
|
||||
);
|
||||
if (hasPressedAltAndMKey(e)) {
|
||||
this.$refs.arrowDownButton.$el.click();
|
||||
if (this.$refs.arrowDownButton) {
|
||||
this.$refs.arrowDownButton.$el.click();
|
||||
}
|
||||
}
|
||||
if (hasPressedAltAndEKey(e)) {
|
||||
const activeConversation = document.querySelector(
|
||||
|
@ -198,6 +200,7 @@ export default {
|
|||
allConversations[0].click();
|
||||
document.querySelector('.conversations-list').scrollTop = 0;
|
||||
}
|
||||
e.preventDefault();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -94,8 +94,16 @@ import AccountSelector from './sidebarComponents/AccountSelector.vue';
|
|||
import AddAccountModal from './sidebarComponents/AddAccountModal.vue';
|
||||
import AddLabelModal from '../../routes/dashboard/settings/labels/AddLabel';
|
||||
import WootKeyShortcutModal from 'components/widgets/modal/WootKeyShortcutModal';
|
||||
import { hasPressedCommandAndForwardSlash } from 'shared/helpers/KeyboardHelpers';
|
||||
import {
|
||||
hasPressedAltAndCKey,
|
||||
hasPressedAltAndRKey,
|
||||
hasPressedAltAndSKey,
|
||||
hasPressedAltAndVKey,
|
||||
hasPressedCommandAndForwardSlash,
|
||||
isEscape,
|
||||
} from 'shared/helpers/KeyboardHelpers';
|
||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||
import router from '../../routes';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
@ -252,19 +260,11 @@ export default {
|
|||
return frontendURL(`accounts/${this.accountId}/dashboard`);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
currentUser(newUserInfo, oldUserInfo) {
|
||||
if (!oldUserInfo.email && newUserInfo.email) {
|
||||
this.setChatwootUser();
|
||||
}
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$store.dispatch('labels/get');
|
||||
this.$store.dispatch('inboxes/get');
|
||||
this.$store.dispatch('notifications/unReadCount');
|
||||
this.$store.dispatch('teams/get');
|
||||
this.setChatwootUser();
|
||||
},
|
||||
|
||||
methods: {
|
||||
|
@ -278,21 +278,34 @@ export default {
|
|||
if (hasPressedCommandAndForwardSlash(e)) {
|
||||
this.toggleKeyShortcutModal();
|
||||
}
|
||||
if (isEscape(e)) {
|
||||
this.closeKeyShortcutModal();
|
||||
}
|
||||
|
||||
if (hasPressedAltAndCKey(e)) {
|
||||
if (!this.isCurrentRouteSameAsNavigation('home')) {
|
||||
router.push({ name: 'home' });
|
||||
}
|
||||
} else if (hasPressedAltAndVKey(e)) {
|
||||
if (!this.isCurrentRouteSameAsNavigation('contacts_dashboard')) {
|
||||
router.push({ name: 'contacts_dashboard' });
|
||||
}
|
||||
} else if (hasPressedAltAndRKey(e)) {
|
||||
if (!this.isCurrentRouteSameAsNavigation('settings_account_reports')) {
|
||||
router.push({ name: 'settings_account_reports' });
|
||||
}
|
||||
} else if (hasPressedAltAndSKey(e)) {
|
||||
if (!this.isCurrentRouteSameAsNavigation('agent_list')) {
|
||||
router.push({ name: 'agent_list' });
|
||||
}
|
||||
}
|
||||
},
|
||||
isCurrentRouteSameAsNavigation(routeName) {
|
||||
return router.currentRoute && router.currentRoute.name === routeName;
|
||||
},
|
||||
toggleSupportChatWindow() {
|
||||
window.$chatwoot.toggle();
|
||||
},
|
||||
setChatwootUser() {
|
||||
if (!this.currentUser.email || !this.globalConfig.chatwootInboxToken) {
|
||||
return;
|
||||
}
|
||||
window.$chatwoot.setUser(this.currentUser.email, {
|
||||
name: this.currentUser.name,
|
||||
email: this.currentUser.email,
|
||||
avatar_url: this.currentUser.avatar_url,
|
||||
identifier_hash: this.currentUser.hmac_identifier,
|
||||
});
|
||||
},
|
||||
filterMenuItemsByRole(menuItems) {
|
||||
if (!this.currentRole) {
|
||||
return [];
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
<a href="#" :class="computedChildClass(child)">
|
||||
<div class="wrap">
|
||||
<i
|
||||
v-if="computedInboxClass(child)"
|
||||
v-if="menuItem.key === 'inbox'"
|
||||
class="inbox-icon"
|
||||
:class="computedInboxClass(child)"
|
||||
/>
|
||||
|
@ -59,17 +59,10 @@
|
|||
import { mapGetters } from 'vuex';
|
||||
|
||||
import router from '../../routes';
|
||||
import {
|
||||
hasPressedAltAndCKey,
|
||||
hasPressedAltAndVKey,
|
||||
hasPressedAltAndRKey,
|
||||
hasPressedAltAndSKey,
|
||||
} from 'shared/helpers/KeyboardHelpers';
|
||||
import adminMixin from '../../mixins/isAdmin';
|
||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||
import { getInboxClassByType } from 'dashboard/helper/inbox';
|
||||
export default {
|
||||
mixins: [adminMixin, eventListenerMixins],
|
||||
mixins: [adminMixin],
|
||||
props: {
|
||||
menuItem: {
|
||||
type: Object,
|
||||
|
@ -124,20 +117,6 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
handleKeyEvents(e) {
|
||||
if (hasPressedAltAndCKey(e)) {
|
||||
router.push({ name: 'home' });
|
||||
}
|
||||
if (hasPressedAltAndVKey(e)) {
|
||||
router.push({ name: 'contacts_dashboard' });
|
||||
}
|
||||
if (hasPressedAltAndRKey(e)) {
|
||||
router.push({ name: 'settings_account_reports' });
|
||||
}
|
||||
if (hasPressedAltAndSKey(e)) {
|
||||
router.push({ name: 'settings_home' });
|
||||
}
|
||||
},
|
||||
showItem(item) {
|
||||
return this.isAdmin && item.newLink !== undefined;
|
||||
},
|
||||
|
|
|
@ -94,10 +94,6 @@ export default {
|
|||
methods: {
|
||||
logout() {
|
||||
Auth.logout();
|
||||
|
||||
if (this.globalConfig.chatwootInboxToken) {
|
||||
window.$chatwoot.reset();
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
@ -76,7 +76,16 @@ export default {
|
|||
if (key === 'email') {
|
||||
return this.enabledFeatures.channel_email;
|
||||
}
|
||||
return ['website', 'twilio', 'api', 'whatsapp', 'sms'].includes(key);
|
||||
|
||||
return [
|
||||
'website',
|
||||
'twilio',
|
||||
'api',
|
||||
'whatsapp',
|
||||
'sms',
|
||||
'telegram',
|
||||
'line',
|
||||
].includes(key);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
|
|
|
@ -1,33 +1,27 @@
|
|||
<template>
|
||||
<div>
|
||||
<h6 class="text-block-title">
|
||||
<i class="title-icon ion-pricetags" />
|
||||
{{ $t('CONTACT_PANEL.LABELS.CONTACT.TITLE') }}
|
||||
</h6>
|
||||
<div v-on-clickaway="closeDropdownLabel" class="label-wrap">
|
||||
<add-label @add="toggleLabels" />
|
||||
<woot-label
|
||||
v-for="label in savedLabels"
|
||||
:key="label.id"
|
||||
:title="label.title"
|
||||
:description="label.description"
|
||||
:show-close="true"
|
||||
:bg-color="label.color"
|
||||
@click="removeItem"
|
||||
/>
|
||||
<div class="dropdown-wrap">
|
||||
<div
|
||||
:class="{ 'dropdown-pane--open': showSearchDropdownLabel }"
|
||||
class="dropdown-pane"
|
||||
>
|
||||
<label-dropdown
|
||||
v-if="showSearchDropdownLabel"
|
||||
:account-labels="allLabels"
|
||||
:selected-labels="selectedLabels"
|
||||
@add="addItem"
|
||||
@remove="removeItem"
|
||||
/>
|
||||
</div>
|
||||
<div v-on-clickaway="closeDropdownLabel" class="label-wrap">
|
||||
<add-label @add="toggleLabels" />
|
||||
<woot-label
|
||||
v-for="label in savedLabels"
|
||||
:key="label.id"
|
||||
:title="label.title"
|
||||
:description="label.description"
|
||||
:show-close="true"
|
||||
:bg-color="label.color"
|
||||
@click="removeItem"
|
||||
/>
|
||||
<div class="dropdown-wrap">
|
||||
<div
|
||||
:class="{ 'dropdown-pane--open': showSearchDropdownLabel }"
|
||||
class="dropdown-pane"
|
||||
>
|
||||
<label-dropdown
|
||||
v-if="showSearchDropdownLabel"
|
||||
:account-labels="allLabels"
|
||||
:selected-labels="selectedLabels"
|
||||
@add="addItem"
|
||||
@remove="removeItem"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -96,7 +90,6 @@ export default {
|
|||
|
||||
.label-wrap {
|
||||
position: relative;
|
||||
margin-left: var(--space-two);
|
||||
line-height: var(--space-medium);
|
||||
|
||||
.dropdown-wrap {
|
||||
|
|
|
@ -35,6 +35,20 @@
|
|||
:style="badgeStyle"
|
||||
src="~dashboard/assets/images/channels/whatsapp.png"
|
||||
/>
|
||||
<img
|
||||
v-if="badge === 'Channel::Line'"
|
||||
id="badge"
|
||||
class="source-badge"
|
||||
:style="badgeStyle"
|
||||
src="~dashboard/assets/images/channels/line.png"
|
||||
/>
|
||||
<img
|
||||
v-if="badge === 'Channel::Telegram'"
|
||||
id="badge"
|
||||
class="source-badge"
|
||||
:style="badgeStyle"
|
||||
src="~dashboard/assets/images/channels/telegram.png"
|
||||
/>
|
||||
<div
|
||||
v-if="showStatusIndicator"
|
||||
:class="`source-badge user-online-status user-online-status--${status}`"
|
||||
|
|
|
@ -38,6 +38,11 @@ import CannedResponse from '../conversation/CannedResponse';
|
|||
const TYPING_INDICATOR_IDLE_TIME = 4000;
|
||||
|
||||
import '@chatwoot/prosemirror-schema/src/woot-editor.css';
|
||||
import {
|
||||
hasPressedAltAndPKey,
|
||||
hasPressedAltAndLKey,
|
||||
} from 'shared/helpers/KeyboardHelpers';
|
||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||
|
||||
const createState = (content, placeholder, plugins = []) => {
|
||||
return EditorState.create({
|
||||
|
@ -53,6 +58,7 @@ const createState = (content, placeholder, plugins = []) => {
|
|||
export default {
|
||||
name: 'WootMessageEditor',
|
||||
components: { TagAgents, CannedResponse },
|
||||
mixins: [eventListenerMixins],
|
||||
props: {
|
||||
value: { type: String, default: '' },
|
||||
placeholder: { type: String, default: '' },
|
||||
|
@ -177,8 +183,20 @@ export default {
|
|||
},
|
||||
},
|
||||
});
|
||||
this.focusEditorInputField();
|
||||
},
|
||||
methods: {
|
||||
handleKeyEvents(e) {
|
||||
if (hasPressedAltAndPKey(e)) {
|
||||
this.focusEditorInputField();
|
||||
}
|
||||
if (hasPressedAltAndLKey(e)) {
|
||||
this.focusEditorInputField();
|
||||
}
|
||||
},
|
||||
focusEditorInputField() {
|
||||
this.$refs.editor.querySelector('div.ProseMirror-woot-style').focus();
|
||||
},
|
||||
insertMentionNode(mentionItem) {
|
||||
if (!this.view) {
|
||||
return null;
|
||||
|
|
|
@ -11,10 +11,11 @@
|
|||
@click="toggleEmojiPicker"
|
||||
/>
|
||||
|
||||
<!-- ensure the same validations for attachment types are implemented in backend models as well -->
|
||||
<file-upload
|
||||
ref="upload"
|
||||
:size="4096 * 4096"
|
||||
accept="image/*, application/pdf, audio/mpeg, video/mp4, audio/ogg, text/csv"
|
||||
accept="image/png, image/jpeg, image/gif, image/bmp, image/tiff, application/pdf, audio/mpeg, video/mp4, audio/ogg, text/csv"
|
||||
:drop="true"
|
||||
:drop-directory="false"
|
||||
@input-file="onFileUpload"
|
||||
|
|
|
@ -26,6 +26,24 @@
|
|||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<woot-button
|
||||
v-if="popoutReplyBox"
|
||||
variant="clear"
|
||||
size="large"
|
||||
icon="ion-android-close"
|
||||
color-scheme="secondary"
|
||||
class-names="popout-button"
|
||||
@click="$emit('click')"
|
||||
/>
|
||||
<woot-button
|
||||
v-else
|
||||
variant="clear"
|
||||
size="large"
|
||||
icon="ion-arrow-resize"
|
||||
color-scheme="secondary"
|
||||
class-names="popout-button"
|
||||
@click="$emit('click')"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -60,6 +78,10 @@ export default {
|
|||
type: Number,
|
||||
default: () => 0,
|
||||
},
|
||||
popoutReplyBox: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
replyButtonClass() {
|
||||
|
@ -105,7 +127,7 @@ export default {
|
|||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
background: var(--b-100);
|
||||
background: var(--b-50);
|
||||
}
|
||||
|
||||
.button-group {
|
||||
|
@ -167,4 +189,11 @@ export default {
|
|||
color: var(--s-600);
|
||||
}
|
||||
}
|
||||
|
||||
.popout-button {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
height: auto;
|
||||
padding-right: var(--space-normal);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -119,7 +119,6 @@ export default {
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
padding: var(--space-normal) var(--space-two);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
:message="message"
|
||||
:is-email="isEmailContentType"
|
||||
:readable-time="readableTime"
|
||||
:display-quoted-button="displayQuotedButton"
|
||||
/>
|
||||
<span
|
||||
v-if="isPending && hasAttachments"
|
||||
|
@ -36,7 +37,6 @@
|
|||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<bubble-actions
|
||||
:id="data.id"
|
||||
:sender="data.sender"
|
||||
|
@ -128,6 +128,24 @@ export default {
|
|||
};
|
||||
},
|
||||
computed: {
|
||||
contentToBeParsed() {
|
||||
const {
|
||||
html_content: { full: fullHTMLContent } = {},
|
||||
text_content: { full: fullTextContent } = {},
|
||||
} = this.contentAttributes.email || {};
|
||||
return fullHTMLContent || fullTextContent || '';
|
||||
},
|
||||
displayQuotedButton() {
|
||||
if (!this.isIncoming) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.contentToBeParsed.includes('<blockquote')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
message() {
|
||||
const botMessageContent = generateBotMessageContent(
|
||||
this.contentType,
|
||||
|
@ -142,20 +160,10 @@ export default {
|
|||
);
|
||||
|
||||
const {
|
||||
email: {
|
||||
content_type: contentType = '',
|
||||
html_content: { full: fullHTMLContent, reply: replyHTMLContent } = {},
|
||||
text_content: { full: fullTextContent, reply: replyTextContent } = {},
|
||||
} = {},
|
||||
email: { content_type: contentType = '' } = {},
|
||||
} = this.contentAttributes;
|
||||
let contentToBeParsed =
|
||||
replyHTMLContent ||
|
||||
replyTextContent ||
|
||||
fullHTMLContent ||
|
||||
fullTextContent ||
|
||||
'';
|
||||
if (contentToBeParsed && this.isIncoming) {
|
||||
const parsedContent = this.stripStyleCharacters(contentToBeParsed);
|
||||
if (this.contentToBeParsed && this.isIncoming) {
|
||||
const parsedContent = this.stripStyleCharacters(this.contentToBeParsed);
|
||||
if (parsedContent) {
|
||||
// This is a temporary fix for line-breaks in text/plain emails
|
||||
// Now, It is not rendered properly in the email preview.
|
||||
|
|
|
@ -78,7 +78,10 @@
|
|||
:is-a-tweet="isATweet"
|
||||
/>
|
||||
</ul>
|
||||
<div class="conversation-footer">
|
||||
<div
|
||||
class="conversation-footer"
|
||||
:class="{ 'modal-mask': isPopoutReplyBox }"
|
||||
>
|
||||
<div v-if="isAnyoneTyping" class="typing-indicator-wrap">
|
||||
<div class="typing-indicator">
|
||||
{{ typingUserNames }}
|
||||
|
@ -90,9 +93,12 @@
|
|||
</div>
|
||||
</div>
|
||||
<reply-box
|
||||
v-on-clickaway="closePopoutReplyBox"
|
||||
:conversation-id="currentChat.id"
|
||||
:is-a-tweet="isATweet"
|
||||
:selected-tweet="selectedTweet"
|
||||
:popout-reply-box="isPopoutReplyBox"
|
||||
@click="showPopoutReplyBox"
|
||||
@scrollToMessage="scrollToBottom"
|
||||
/>
|
||||
</div>
|
||||
|
@ -110,13 +116,16 @@ import { BUS_EVENTS } from 'shared/constants/busEvents';
|
|||
import { REPLY_POLICY } from 'shared/constants/links';
|
||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||
import { calculateScrollTop } from './helpers/scrollTopCalculationHelper';
|
||||
import { isEscape } from 'shared/helpers/KeyboardHelpers';
|
||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Message,
|
||||
ReplyBox,
|
||||
},
|
||||
mixins: [conversationMixin, inboxMixin],
|
||||
mixins: [conversationMixin, inboxMixin, eventListenerMixins, clickaway],
|
||||
props: {
|
||||
isContactPanelOpen: {
|
||||
type: Boolean,
|
||||
|
@ -130,6 +139,7 @@ export default {
|
|||
heightBeforeLoad: null,
|
||||
conversationPanel: null,
|
||||
selectedTweetId: null,
|
||||
isPopoutReplyBox: false,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -252,6 +262,17 @@ export default {
|
|||
},
|
||||
|
||||
methods: {
|
||||
showPopoutReplyBox() {
|
||||
this.isPopoutReplyBox = !this.isPopoutReplyBox;
|
||||
},
|
||||
closePopoutReplyBox() {
|
||||
this.isPopoutReplyBox = false;
|
||||
},
|
||||
handleKeyEvents(e) {
|
||||
if (isEscape(e)) {
|
||||
this.closePopoutReplyBox();
|
||||
}
|
||||
},
|
||||
addScrollListener() {
|
||||
this.conversationPanel = this.$el.querySelector('.conversation-panel');
|
||||
this.setScrollParams();
|
||||
|
@ -361,4 +382,39 @@ export default {
|
|||
flex-grow: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.modal-mask {
|
||||
&::v-deep {
|
||||
.ProseMirror-woot-style {
|
||||
max-height: 40rem;
|
||||
}
|
||||
|
||||
.reply-box {
|
||||
border: 1px solid var(--color-border);
|
||||
max-width: 120rem;
|
||||
width: 70%;
|
||||
}
|
||||
|
||||
.reply-box .reply-box__top {
|
||||
position: relative;
|
||||
min-height: 44rem;
|
||||
}
|
||||
|
||||
.reply-box__top .input {
|
||||
min-height: 44rem;
|
||||
}
|
||||
|
||||
.emoji-dialog {
|
||||
position: fixed;
|
||||
left: unset;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.emoji-dialog::before {
|
||||
transform: rotate(0deg);
|
||||
left: 5px;
|
||||
bottom: var(--space-minus-slab);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
</p>
|
||||
<p>
|
||||
<a
|
||||
href="https://changelog.chatwoot.com"
|
||||
href="https://www.chatwoot.com/changelog"
|
||||
target="_blank"
|
||||
rel="noopener nofollow noreferrer"
|
||||
class="onboarding--link"
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
:set-reply-mode="setReplyMode"
|
||||
:is-message-length-reaching-threshold="isMessageLengthReachingThreshold"
|
||||
:characters-remaining="charactersRemaining"
|
||||
:popout-reply-box="popoutReplyBox"
|
||||
@click="$emit('click')"
|
||||
/>
|
||||
<div class="reply-box__top">
|
||||
<canned-response
|
||||
|
@ -74,7 +76,6 @@
|
|||
import { mapGetters } from 'vuex';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||
|
||||
import EmojiInput from 'shared/components/emoji/EmojiInput';
|
||||
import CannedResponse from './CannedResponse';
|
||||
|
@ -106,13 +107,7 @@ export default {
|
|||
ReplyBottomPanel,
|
||||
WootMessageEditor,
|
||||
},
|
||||
mixins: [
|
||||
clickaway,
|
||||
inboxMixin,
|
||||
uiSettingsMixin,
|
||||
alertMixin,
|
||||
eventListenerMixins,
|
||||
],
|
||||
mixins: [clickaway, inboxMixin, uiSettingsMixin, alertMixin],
|
||||
props: {
|
||||
selectedTweet: {
|
||||
type: [Object, String],
|
||||
|
@ -122,6 +117,10 @@ export default {
|
|||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
popoutReplyBox: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -222,7 +221,9 @@ export default {
|
|||
this.isAWebWidgetInbox ||
|
||||
this.isAFacebookInbox ||
|
||||
this.isATwilioWhatsappChannel ||
|
||||
this.isAPIInbox
|
||||
this.isAPIInbox ||
|
||||
this.isAnEmailChannel ||
|
||||
this.isATwilioSMSChannel
|
||||
);
|
||||
},
|
||||
replyButtonLabel() {
|
||||
|
@ -296,6 +297,15 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
|
||||
mounted() {
|
||||
// Donot use the keyboard listener mixin here as the events here are supposed to be
|
||||
// working even if input/textarea is focussed.
|
||||
document.addEventListener('keydown', this.handleKeyEvents);
|
||||
},
|
||||
destroyed() {
|
||||
document.removeEventListener('keydown', this.handleKeyEvents);
|
||||
},
|
||||
methods: {
|
||||
toggleUserMention(currentMentionState) {
|
||||
this.hasUserMention = currentMentionState;
|
||||
|
@ -354,9 +364,6 @@ export default {
|
|||
if (this.showRichContentEditor) {
|
||||
return;
|
||||
}
|
||||
if (this.$refs.messageInput === undefined) {
|
||||
return;
|
||||
}
|
||||
this.$nextTick(() => this.$refs.messageInput.focus());
|
||||
},
|
||||
emojiOnClick(emoji) {
|
||||
|
@ -390,13 +397,11 @@ export default {
|
|||
this.isFocused = true;
|
||||
},
|
||||
toggleTyping(status) {
|
||||
if (this.isAWebWidgetInbox && !this.isPrivate) {
|
||||
const conversationId = this.currentChat.id;
|
||||
this.$store.dispatch('conversationTypingStatus/toggleTyping', {
|
||||
status,
|
||||
conversationId,
|
||||
});
|
||||
}
|
||||
const conversationId = this.currentChat.id;
|
||||
this.$store.dispatch('conversationTypingStatus/toggleTyping', {
|
||||
status,
|
||||
conversationId,
|
||||
});
|
||||
},
|
||||
onFileUpload(file) {
|
||||
if (!file) {
|
||||
|
|
|
@ -1,6 +1,26 @@
|
|||
<template>
|
||||
<div class="message-text__wrap">
|
||||
<div
|
||||
class="message-text__wrap"
|
||||
:class="{
|
||||
'show--quoted': showQuotedContent,
|
||||
'hide--quoted': !showQuotedContent,
|
||||
}"
|
||||
>
|
||||
<div class="text-content" v-html="message"></div>
|
||||
<button
|
||||
v-if="displayQuotedButton"
|
||||
class="quoted-text--button"
|
||||
@click="toggleQuotedContent"
|
||||
>
|
||||
<span v-if="showQuotedContent">
|
||||
<i class="ion-chevron-up" />
|
||||
{{ $t('CHAT_LIST.HIDE_QUOTED_TEXT') }}
|
||||
</span>
|
||||
<span v-else>
|
||||
<i class="ion-chevron-down" />
|
||||
{{ $t('CHAT_LIST.SHOW_QUOTED_TEXT') }}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -19,10 +39,24 @@ export default {
|
|||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
displayQuotedButton: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
showQuotedContent: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
toggleQuotedContent() {
|
||||
this.showQuotedContent = !this.showQuotedContent;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
<style lang="scss">
|
||||
.text-content {
|
||||
overflow: auto;
|
||||
|
||||
|
@ -32,5 +66,45 @@ export default {
|
|||
margin-left: var(--space-normal);
|
||||
}
|
||||
}
|
||||
table {
|
||||
all: revert;
|
||||
|
||||
td {
|
||||
all: revert;
|
||||
}
|
||||
|
||||
tr {
|
||||
all: revert;
|
||||
}
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-size: var(--font-size-normal);
|
||||
}
|
||||
}
|
||||
|
||||
.show--quoted {
|
||||
blockquote {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.hide--quoted {
|
||||
blockquote {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.quoted-text--button {
|
||||
color: var(--s-400);
|
||||
cursor: pointer;
|
||||
font-size: var(--font-size-mini);
|
||||
padding-bottom: var(--space-small);
|
||||
padding-top: var(--space-small);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,16 +1,32 @@
|
|||
<template>
|
||||
<label>
|
||||
<span v-if="label">{{ label }}</span>
|
||||
<div>
|
||||
<label>
|
||||
<span v-if="label">{{ label }}</span>
|
||||
</label>
|
||||
<woot-thumbnail v-if="src" size="80px" :src="src" />
|
||||
<input
|
||||
id="file"
|
||||
ref="file"
|
||||
type="file"
|
||||
accept="image/*"
|
||||
@change="handleImageUpload"
|
||||
/>
|
||||
<slot></slot>
|
||||
</label>
|
||||
<div v-if="src && deleteAvatar" class="avatar-delete-btn">
|
||||
<woot-button
|
||||
color-scheme="alert"
|
||||
variant="hollow"
|
||||
size="tiny"
|
||||
@click="onAvatarDelete"
|
||||
>{{
|
||||
this.$t('INBOX_MGMT.DELETE.AVATAR_DELETE_BUTTON_TEXT')
|
||||
}}</woot-button
|
||||
>
|
||||
</div>
|
||||
<label>
|
||||
<input
|
||||
id="file"
|
||||
ref="file"
|
||||
type="file"
|
||||
accept="image/png, image/jpeg, image/gif"
|
||||
@change="handleImageUpload"
|
||||
/>
|
||||
<slot></slot>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
@ -24,6 +40,10 @@ export default {
|
|||
type: String,
|
||||
default: '',
|
||||
},
|
||||
deleteAvatar: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
watch: {},
|
||||
methods: {
|
||||
|
@ -35,6 +55,17 @@ export default {
|
|||
url: URL.createObjectURL(file),
|
||||
});
|
||||
},
|
||||
onAvatarDelete() {
|
||||
this.$refs.file.value = null;
|
||||
this.$emit('onAvatarDelete');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.avatar-delete-btn {
|
||||
margin-top: var(--space-smaller);
|
||||
margin-bottom: var(--space-smaller);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</h2>
|
||||
<div class="shortcut-key__wrap">
|
||||
<p class="shortcut-key">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.COMMAND_KEY') }}
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.WINDOWS_KEY_AND_COMMAND_KEY') }}
|
||||
</p>
|
||||
<p class="shortcut-key key">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.FORWARD_SLASH_KEY') }}
|
||||
|
@ -51,7 +51,7 @@
|
|||
</span>
|
||||
<div class="shortcut-key__wrap">
|
||||
<span class="shortcut-key">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.COMMAND_KEY') }}
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.WINDOWS_KEY_AND_COMMAND_KEY') }}
|
||||
</span>
|
||||
<span class="shortcut-key">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.ALT_OR_OPTION_KEY') }}
|
||||
|
|
|
@ -61,3 +61,10 @@ export const createPendingMessage = data => {
|
|||
|
||||
return pendingMessage;
|
||||
};
|
||||
|
||||
export const convertToSlug = text => {
|
||||
return text
|
||||
.toLowerCase()
|
||||
.replace(/[^\w ]+/g, '')
|
||||
.replace(/ +/g, '_');
|
||||
};
|
||||
|
|
|
@ -22,7 +22,10 @@ export const getInboxClassByType = (type, phoneNumber) => {
|
|||
case INBOX_TYPES.EMAIL:
|
||||
return 'ion-ios-email';
|
||||
|
||||
case INBOX_TYPES.TELEGRAM:
|
||||
return 'ion-ios-navigate';
|
||||
|
||||
default:
|
||||
return '';
|
||||
return 'ion-ios-chatbubble';
|
||||
}
|
||||
};
|
||||
|
|
43
app/javascript/dashboard/helper/scriptHelpers.js
Normal file
43
app/javascript/dashboard/helper/scriptHelpers.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
import posthog from 'posthog-js';
|
||||
|
||||
export const CHATWOOT_SET_USER = 'CHATWOOT_SET_USER';
|
||||
export const CHATWOOT_RESET = 'CHATWOOT_RESET';
|
||||
|
||||
export const ANALYTICS_IDENTITY = 'ANALYTICS_IDENTITY';
|
||||
export const ANALYTICS_RESET = 'ANALYTICS_RESET';
|
||||
|
||||
export const initializeAnalyticsEvents = () => {
|
||||
window.bus.$on(ANALYTICS_IDENTITY, ({ user }) => {
|
||||
if (window.analyticsConfig) {
|
||||
posthog.identify(user.id, { name: user.name, email: user.email });
|
||||
}
|
||||
});
|
||||
|
||||
window.bus.$on(ANALYTICS_RESET, () => {
|
||||
if (window.analyticsConfig) {
|
||||
posthog.reset();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const initializeChatwootEvents = () => {
|
||||
window.bus.$on(CHATWOOT_RESET, () => {
|
||||
if (window.$chatwoot) {
|
||||
window.$chatwoot.reset();
|
||||
}
|
||||
});
|
||||
window.bus.$on(CHATWOOT_SET_USER, ({ user }) => {
|
||||
if (window.$chatwoot) {
|
||||
window.$chatwoot.setUser(user.email, {
|
||||
avatar_url: user.avatar_url,
|
||||
email: user.email,
|
||||
identifier_hash: user.hmac_identifier,
|
||||
name: user.name,
|
||||
});
|
||||
window.$chatwoot.setCustomAttributes({
|
||||
signedUpAt: user.created_at,
|
||||
cloudCustomer: 'true',
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
|
@ -1,4 +1,8 @@
|
|||
import { getTypingUsersText, createPendingMessage } from '../commons';
|
||||
import {
|
||||
getTypingUsersText,
|
||||
createPendingMessage,
|
||||
convertToSlug,
|
||||
} from '../commons';
|
||||
|
||||
describe('#getTypingUsersText', () => {
|
||||
it('returns the correct text is there is only one typing user', () => {
|
||||
|
@ -83,3 +87,9 @@ describe('#createPendingMessage', () => {
|
|||
expect(pending.attachments.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('convertToSlug', () => {
|
||||
it('should convert to slug', () => {
|
||||
expect(convertToSlug('Test@%^&*(){}>.!@`~_ ing')).toBe('test__ing');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -144,6 +144,7 @@ export const getSidebarItems = accountId => ({
|
|||
'canned_list',
|
||||
'labels_list',
|
||||
'settings_inbox',
|
||||
'attributes_list',
|
||||
'settings_inbox_new',
|
||||
'settings_inbox_list',
|
||||
'settings_inbox_show',
|
||||
|
@ -202,6 +203,13 @@ export const getSidebarItems = accountId => ({
|
|||
toState: frontendURL(`accounts/${accountId}/settings/labels/list`),
|
||||
toStateName: 'labels_list',
|
||||
},
|
||||
attributes: {
|
||||
icon: 'ion-code',
|
||||
label: 'ATTRIBUTES',
|
||||
hasSubMenu: false,
|
||||
toState: frontendURL(`accounts/${accountId}/settings/attributes/list`),
|
||||
toStateName: 'attributes_list',
|
||||
},
|
||||
cannedResponses: {
|
||||
icon: 'ion-chatbox-working',
|
||||
label: 'CANNED_RESPONSES',
|
||||
|
|
|
@ -91,6 +91,23 @@
|
|||
},
|
||||
"SEARCH": {
|
||||
"NO_RESULTS": "لم يتم العثور على النتائج."
|
||||
},
|
||||
"MULTI_SELECTOR": {
|
||||
"PLACEHOLDER": "لا شيء",
|
||||
"TITLE": {
|
||||
"AGENT": "اختر وكيل",
|
||||
"TEAM": "اختيار فريق"
|
||||
},
|
||||
"SEARCH": {
|
||||
"NO_RESULTS": {
|
||||
"AGENT": "لم يتم العثور على موظفين",
|
||||
"TEAM": "لم يتم العثور على موظفين"
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"AGENT": "البحث عن وكلاء",
|
||||
"TEAM": "البحث عن فريق"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
85
app/javascript/dashboard/i18n/locale/ar/attributesMgmt.json
Normal file
85
app/javascript/dashboard/i18n/locale/ar/attributesMgmt.json
Normal file
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"ATTRIBUTES_MGMT": {
|
||||
"HEADER": "السمات",
|
||||
"HEADER_BTN_TXT": "إضافة سمة",
|
||||
"LOADING": "Fetching attributes",
|
||||
"SIDEBAR_TXT": "<p><b>Attributes</b> <p>A custom attribute tracks facts about your contacts/conversation — like the subscription plan, or when they ordered the first item etc. <br /><br />For creating a Attributes, just click on the <b>Add Attribute.</b> You can also edit or delete an existing Attribute by clicking on the Edit or Delete button.</p>",
|
||||
"ADD": {
|
||||
"TITLE": "أضف سمة",
|
||||
"SUBMIT": "إنشاء",
|
||||
"CANCEL_BUTTON_TEXT": "إلغاء",
|
||||
"FORM": {
|
||||
"NAME": {
|
||||
"LABEL": "اسم العرض",
|
||||
"PLACEHOLDER": "أدخل اسم عرض السمة",
|
||||
"ERROR": "Name is required"
|
||||
},
|
||||
"DESC": {
|
||||
"LABEL": "الوصف",
|
||||
"PLACEHOLDER": "أدخل وصف السمة",
|
||||
"ERROR": "Description is required"
|
||||
},
|
||||
"MODEL": {
|
||||
"LABEL": "نموذج",
|
||||
"PLACEHOLDER": "الرجاء اختيار نموذج",
|
||||
"ERROR": "النموذج مطلوب"
|
||||
},
|
||||
"TYPE": {
|
||||
"LABEL": "النوع",
|
||||
"PLACEHOLDER": "الرجاء تحديد نوع",
|
||||
"ERROR": "النوع مطلوب"
|
||||
},
|
||||
"KEY": {
|
||||
"LABEL": "المفتاح"
|
||||
}
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "تمت إضافة السمة بنجاح",
|
||||
"ERROR_MESSAGE": "Could not able to create an attribute, Please try again later"
|
||||
}
|
||||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "حذف",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Attribute deleted successfully.",
|
||||
"ERROR_MESSAGE": "Couldn't delete the attribute. Try again."
|
||||
},
|
||||
"CONFIRM": {
|
||||
"TITLE": "هل أنت متأكد من أنك تريد حذف - %{attributeName}",
|
||||
"PLACE_HOLDER": "Please type {attributeName} to confirm",
|
||||
"MESSAGE": "Deleting will remove the attribute",
|
||||
"YES": "حذف ",
|
||||
"NO": "إلغاء"
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"TITLE": "Edit attribute",
|
||||
"UPDATE_BUTTON_TEXT": "تحديث",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Attribute updated successfully",
|
||||
"ERROR_MESSAGE": "There was an error updating attribute, please try again"
|
||||
}
|
||||
},
|
||||
"TABS": {
|
||||
"HEADER": "سمات مخصصة",
|
||||
"CONVERSATION": "Conversation",
|
||||
"CONTACT": "جهات الاتصال"
|
||||
},
|
||||
"LIST": {
|
||||
"TABLE_HEADER": [
|
||||
"الاسم",
|
||||
"الوصف",
|
||||
"النوع",
|
||||
"المفتاح"
|
||||
],
|
||||
"BUTTONS": {
|
||||
"EDIT": "تعديل",
|
||||
"DELETE": "حذف"
|
||||
},
|
||||
"EMPTY_RESULT": {
|
||||
"404": "There are no attributes created",
|
||||
"NOT_FOUND": "There are no attributes configured"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,56 +1,64 @@
|
|||
{
|
||||
"CAMPAIGN": {
|
||||
"HEADER": "Campaigns",
|
||||
"SIDEBAR_TXT": "Proactive messages allow the customer to send outbound messages to their contacts which would trigger more conversations. Click on <b>Add Campaign</b> to create a new campaign. You can also edit or delete an existing campaign by clicking on the Edit or Delete button.",
|
||||
"HEADER_BTN_TXT": "Create a campaign",
|
||||
"HEADER": "الحملات",
|
||||
"SIDEBAR_TXT": "الرسائل الاستباقية تسمح للعميل بإرسال رسائل صادرة إلى جهات اتصاله التي من شأنها أن تشغل المزيد من المحادثات. انقر فوق <b>أضف الحملة</b> لإنشاء حملة جديدة. يمكنك أيضا تعديل أو حذف حملة موجودة عن طريق النقر على زر التحرير أو الحذف.",
|
||||
"HEADER_BTN_TXT": {
|
||||
"ONE_OFF": "إنشاء حملة واحدة مغلقة",
|
||||
"ONGOING": "إنشاء حملة مستمرة"
|
||||
},
|
||||
"ADD": {
|
||||
"TITLE": "Create a campaign",
|
||||
"DESC": "Proactive messages allow the customer to send outbound messages to their contacts which would trigger more conversations.",
|
||||
"TITLE": "إنشاء حملة",
|
||||
"DESC": "الرسائل الاستباقية تسمح للعميل بإرسال رسائل صادرة إلى جهات اتصاله التي من شأنها أن تشغل المزيد من المحادثات.",
|
||||
"CANCEL_BUTTON_TEXT": "إلغاء",
|
||||
"CREATE_BUTTON_TEXT": "إنشاء",
|
||||
"FORM": {
|
||||
"TITLE": {
|
||||
"LABEL": "Title",
|
||||
"PLACEHOLDER": "Please enter the title of campaign",
|
||||
"ERROR": "Title is required"
|
||||
"LABEL": "العنوان",
|
||||
"PLACEHOLDER": "الرجاء إدخال عنوان الحملة",
|
||||
"ERROR": "العنوان مطلوب"
|
||||
},
|
||||
"SCHEDULED_AT": {
|
||||
"LABEL": "Scheduled time",
|
||||
"PLACEHOLDER": "Please select the time",
|
||||
"CONFIRM": "Confirm",
|
||||
"ERROR": "Scheduled time is required"
|
||||
"LABEL": "الوقت المجدول",
|
||||
"PLACEHOLDER": "الرجاء اختيار الوقت",
|
||||
"CONFIRM": "تأكيد",
|
||||
"ERROR": "الوقت المجدول مطلوب"
|
||||
},
|
||||
"AUDIENCE": {
|
||||
"LABEL": "Audience",
|
||||
"PLACEHOLDER": "Select the customer labels",
|
||||
"ERROR": "Audience is required"
|
||||
"LABEL": "الجمهور",
|
||||
"PLACEHOLDER": "حدد أوسمة العملاء",
|
||||
"ERROR": "الجمهور مطلوب"
|
||||
},
|
||||
"INBOX": {
|
||||
"LABEL": "اختر صندوق الوارد",
|
||||
"PLACEHOLDER": "اختر صندوق الوارد",
|
||||
"ERROR": "صندوق الوارد مطلوب"
|
||||
},
|
||||
"MESSAGE": {
|
||||
"LABEL": "رسالة",
|
||||
"PLACEHOLDER": "Please enter the message of campaign",
|
||||
"ERROR": "Message is required"
|
||||
"PLACEHOLDER": "الرجاء إدخال رسالة الحملة",
|
||||
"ERROR": "الرسالة مطلوبة"
|
||||
},
|
||||
"SENT_BY": {
|
||||
"LABEL": "أرسلت بواسطة",
|
||||
"PLACEHOLDER": "Please select the the content of campaign",
|
||||
"ERROR": "Sender is required"
|
||||
"PLACEHOLDER": "الرجاء تحديد محتوى الحملة",
|
||||
"ERROR": "المرسل مطلوب"
|
||||
},
|
||||
"END_POINT": {
|
||||
"LABEL": "URL",
|
||||
"PLACEHOLDER": "Please enter the URL",
|
||||
"LABEL": "الرابط",
|
||||
"PLACEHOLDER": "الرجاء إدخال الرابط",
|
||||
"ERROR": "الرجاء إدخال عنوان URL صالح"
|
||||
},
|
||||
"TIME_ON_PAGE": {
|
||||
"LABEL": "Time on page(Seconds)",
|
||||
"PLACEHOLDER": "Please enter the time",
|
||||
"ERROR": "Time on page is required"
|
||||
"LABEL": "الوقت على الصفحة (ثواني)",
|
||||
"PLACEHOLDER": "الرجاء إدخال الوقت",
|
||||
"ERROR": "الوقت على الصفحة مطلوب"
|
||||
},
|
||||
"ENABLED": "Enable campaign",
|
||||
"SUBMIT": "Add Campaign"
|
||||
"ENABLED": "تفعيل الحملة",
|
||||
"SUBMIT": "إضافة حملة"
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Campaign created successfully",
|
||||
"ERROR_MESSAGE": "There was an error. Please try again."
|
||||
"SUCCESS_MESSAGE": "تم إنشاء الحملة بنجاح",
|
||||
"ERROR_MESSAGE": "حدث خطأ، الرجاء المحاولة مرة أخرى."
|
||||
}
|
||||
},
|
||||
"DELETE": {
|
||||
|
@ -62,45 +70,56 @@
|
|||
"NO": "لا، احتفظ "
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Campaign deleted successfully",
|
||||
"ERROR_MESSAGE": "Could not delete the campaign. Please try again later."
|
||||
"SUCCESS_MESSAGE": "تم حذف الحملة بنجاح",
|
||||
"ERROR_MESSAGE": "تعذر حذف الحملة. الرجاء المحاولة مرة أخرى لاحقاً."
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"TITLE": "Edit campaign",
|
||||
"TITLE": "تعديل الحملة",
|
||||
"UPDATE_BUTTON_TEXT": "تحديث",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Campaign updated successfully",
|
||||
"SUCCESS_MESSAGE": "تم تحديث الحملة بنجاح",
|
||||
"ERROR_MESSAGE": "حدث خطأ، الرجاء المحاولة مرة أخرى"
|
||||
}
|
||||
},
|
||||
"LIST": {
|
||||
"LOADING_MESSAGE": "Loading campaigns...",
|
||||
"404": "There are no campaigns created for this inbox.",
|
||||
"LOADING_MESSAGE": "جاري تحميل الحملات...",
|
||||
"404": "لا توجد حملات منشئة لهذا البريد الوارد.",
|
||||
"TABLE_HEADER": {
|
||||
"TITLE": "Title",
|
||||
"TITLE": "العنوان",
|
||||
"MESSAGE": "رسالة",
|
||||
"INBOX": "صندوق الوارد",
|
||||
"STATUS": "الحالة",
|
||||
"SENDER": "Sender",
|
||||
"URL": "URL",
|
||||
"SCHEDULED_AT": "Scheduled time",
|
||||
"TIME_ON_PAGE": "Time(Seconds)",
|
||||
"CREATED_AT": "Created at"
|
||||
"SENDER": "المرسل",
|
||||
"URL": "الرابط",
|
||||
"SCHEDULED_AT": "الوقت المجدول",
|
||||
"TIME_ON_PAGE": "الوقت(ثواني)",
|
||||
"CREATED_AT": "تم إنشاؤها في"
|
||||
},
|
||||
"BUTTONS": {
|
||||
"ADD": "Add",
|
||||
"ADD": "إضافة",
|
||||
"EDIT": "تعديل",
|
||||
"DELETE": "حذف"
|
||||
},
|
||||
"STATUS": {
|
||||
"ENABLED": "مفعل",
|
||||
"DISABLED": "معطّل",
|
||||
"COMPLETED": "Completed",
|
||||
"ACTIVE": "Active"
|
||||
"COMPLETED": "مكتمل",
|
||||
"ACTIVE": "مفعل"
|
||||
},
|
||||
"SENDER": {
|
||||
"BOT": "رد آلي"
|
||||
}
|
||||
},
|
||||
"ONE_OFF": {
|
||||
"HEADER": "حملة واحدة مغلقة",
|
||||
"404": "لم يتم إنشاء أي حملة ",
|
||||
"INBOXES_NOT_FOUND": "الرجاء إنشاء بريد وارد للرسائل القصيرة ثم إبدء في إضافة حملات"
|
||||
},
|
||||
"ONGOING": {
|
||||
"HEADER": "الحملات المستمرة",
|
||||
"404": "لا توجد حملات مستمرة منشئة",
|
||||
"INBOXES_NOT_FOUND": "الرجاء إنشاء بريد وارد لمحادثات الموقع الحية ثم إبدء في إضافة حملات"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,8 +47,12 @@
|
|||
"VALUE": "resolved"
|
||||
},
|
||||
{
|
||||
"TEXT": "رد آلي",
|
||||
"VALUE": "bot"
|
||||
"TEXT": "معلق",
|
||||
"VALUE": "pending"
|
||||
},
|
||||
{
|
||||
"TEXT": "غفوة",
|
||||
"VALUE": "snoozed"
|
||||
}
|
||||
],
|
||||
"ATTACHMENTS": {
|
||||
|
@ -81,6 +85,6 @@
|
|||
"VIEW_TWEET_IN_TWITTER": "عرض التغريدة في تويتر",
|
||||
"REPLY_TO_TWEET": "الرد على هذه التغريدة",
|
||||
"NO_MESSAGES": "لا توجد رسائل",
|
||||
"NO_CONTENT": "No content available"
|
||||
"NO_CONTENT": "لم يتم العثور على محتوى"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,24 +12,24 @@
|
|||
"INITIATED_FROM": "تم البدء من",
|
||||
"INITIATED_AT": "تم البدء في",
|
||||
"IP_ADDRESS": "عنوان IP",
|
||||
"NEW_MESSAGE": "New message",
|
||||
"NEW_MESSAGE": "رسالة جديدة",
|
||||
"CONVERSATIONS": {
|
||||
"NO_RECORDS_FOUND": "لا توجد محادثات سابقة مرتبطة بجهة الاتصال هذه.",
|
||||
"TITLE": "المحادثات السابقة"
|
||||
},
|
||||
"LABELS": {
|
||||
"CONTACT": {
|
||||
"TITLE": "Contact Labels",
|
||||
"ERROR": "Couldn't update labels"
|
||||
"TITLE": "تصنفيات جهات الاتصال",
|
||||
"ERROR": "تعذر تحديث التصنيفات"
|
||||
},
|
||||
"CONVERSATION": {
|
||||
"TITLE": "وسوم المحادثة",
|
||||
"ADD_BUTTON": "Add Labels"
|
||||
"ADD_BUTTON": "اضافة تصنيف جديد"
|
||||
},
|
||||
"LABEL_SELECT": {
|
||||
"TITLE": "Add Labels",
|
||||
"PLACEHOLDER": "Search labels",
|
||||
"NO_RESULT": "No labels found"
|
||||
"TITLE": "اضافة تصنيف جديد",
|
||||
"PLACEHOLDER": "ابحث عن تصنيفات",
|
||||
"NO_RESULT": "لم يتم العثور على تصنيفات"
|
||||
}
|
||||
},
|
||||
"MUTE_CONTACT": "كتم المحادثة",
|
||||
|
@ -37,7 +37,12 @@
|
|||
"MUTED_SUCCESS": "تم كتم هذه المحادثة لمدة 6 ساعات",
|
||||
"UNMUTED_SUCCESS": "تم إلغاء كتم هذه المحادثة",
|
||||
"SEND_TRANSCRIPT": "إرسال النص",
|
||||
"EDIT_LABEL": "تعديل"
|
||||
"EDIT_LABEL": "تعديل",
|
||||
"SIDEBAR_SECTIONS": {
|
||||
"CUSTOM_ATTRIBUTES": "سمات مخصصة",
|
||||
"CONTACT_LABELS": "تصنفيات جهات الاتصال",
|
||||
"PREVIOUS_CONVERSATIONS": "المحادثات السابقة"
|
||||
}
|
||||
},
|
||||
"EDIT_CONTACT": {
|
||||
"BUTTON_LABEL": "تعديل جهة الاتصال",
|
||||
|
@ -71,8 +76,8 @@
|
|||
"PHONE_NUMBER": {
|
||||
"PLACEHOLDER": "أدخل رقم الهاتف الخاص بجهة الاتصال",
|
||||
"LABEL": "رقم الهاتف",
|
||||
"HELP": "Phone number should be of E.164 format eg: +1415555555 [+][country code][area code][local phone number]",
|
||||
"ERROR": "Phone number should be either empty or of E.164 format"
|
||||
"HELP": "يجب ان يحتوى رقم الهاتف على كود دولتك تسبقها علامة +\nمثال: +20101243567",
|
||||
"ERROR": "يجب ان تكون خانة رقم الهاتف إما فارغة او مكتملة مع رمز الدولة"
|
||||
},
|
||||
"LOCATION": {
|
||||
"PLACEHOLDER": "أدخل موقع جهة الاتصال",
|
||||
|
@ -106,38 +111,38 @@
|
|||
"ERROR_MESSAGE": "حدث خطأ، الرجاء المحاولة مرة أخرى"
|
||||
},
|
||||
"NEW_CONVERSATION": {
|
||||
"BUTTON_LABEL": "Start conversation",
|
||||
"BUTTON_LABEL": "محادثة جديدة",
|
||||
"TITLE": "محادثة جديدة",
|
||||
"DESC": "Start a new conversation by sending a new message.",
|
||||
"NO_INBOX": "Couldn't find an inbox to initiate a new conversation with this contact.",
|
||||
"DESC": "بدء محادثة جديدة بإرسال رسالة جديدة.",
|
||||
"NO_INBOX": "تعذر العثور على صندوق الوارد لبدء محادثة جديدة مع جهة الاتصال هذه.",
|
||||
"FORM": {
|
||||
"TO": {
|
||||
"LABEL": "To"
|
||||
"LABEL": "إلى"
|
||||
},
|
||||
"INBOX": {
|
||||
"LABEL": "Inbox",
|
||||
"ERROR": "Select an inbox"
|
||||
"LABEL": "صندوق الوارد",
|
||||
"ERROR": "حدد صندوق الوارد"
|
||||
},
|
||||
"MESSAGE": {
|
||||
"LABEL": "رسالة",
|
||||
"PLACEHOLDER": "Write your message here",
|
||||
"ERROR": "Message can't be empty"
|
||||
"PLACEHOLDER": "اكتب رسالتك هنا",
|
||||
"ERROR": "لا يمكن أن تكون الرسالة فارغة"
|
||||
},
|
||||
"SUBMIT": "Send message",
|
||||
"SUBMIT": "إرسال الرسالة",
|
||||
"CANCEL": "إلغاء",
|
||||
"SUCCESS_MESSAGE": "Message sent!",
|
||||
"ERROR_MESSAGE": "Couldn't send! try again"
|
||||
"SUCCESS_MESSAGE": "تم إرسال الرسالة!",
|
||||
"ERROR_MESSAGE": "تعذر الإرسال! حاول مرة أخرى"
|
||||
}
|
||||
},
|
||||
"CONTACTS_PAGE": {
|
||||
"HEADER": "جهات الاتصال",
|
||||
"FIELDS": "Contact fields",
|
||||
"FIELDS": "تصنفيات جهات الاتصال",
|
||||
"SEARCH_BUTTON": "بحث",
|
||||
"SEARCH_INPUT_PLACEHOLDER": "بحث عن جهات الاتصال",
|
||||
"LIST": {
|
||||
"LOADING_MESSAGE": "جاري تحميل جهات الاتصال...",
|
||||
"404": "لا توجد جهات اتصال تطابق بحثك 🔍",
|
||||
"NO_CONTACTS": "There are no available contacts",
|
||||
"NO_CONTACTS": "لا توجد جهات اتصال متوفرة",
|
||||
"TABLE_HEADER": {
|
||||
"NAME": "الاسم",
|
||||
"PHONE_NUMBER": "رقم الهاتف",
|
||||
|
@ -154,84 +159,84 @@
|
|||
},
|
||||
"REMINDER": {
|
||||
"ADD_BUTTON": {
|
||||
"BUTTON": "Add",
|
||||
"TITLE": "Shift + Enter to create a task"
|
||||
"BUTTON": "إضافة",
|
||||
"TITLE": "Shift + Enter لإنشاء مهمة"
|
||||
},
|
||||
"FOOTER": {
|
||||
"DUE_DATE": "Due date",
|
||||
"LABEL_TITLE": "Set type"
|
||||
"DUE_DATE": "تاريخ التسليم",
|
||||
"LABEL_TITLE": "تعيين النوع"
|
||||
}
|
||||
},
|
||||
"NOTES": {
|
||||
"HEADER": {
|
||||
"TITLE": "Notes"
|
||||
"TITLE": "ملاحظات"
|
||||
},
|
||||
"ADD": {
|
||||
"BUTTON": "Add",
|
||||
"PLACEHOLDER": "Add a note",
|
||||
"TITLE": "Shift + Enter to create a note"
|
||||
"BUTTON": "إضافة",
|
||||
"PLACEHOLDER": "إضافة ملاحظة",
|
||||
"TITLE": "Shift + Enter لإنشاء مهمة"
|
||||
},
|
||||
"FOOTER": {
|
||||
"BUTTON": "View all notes"
|
||||
"BUTTON": "عرض جميع الملاحظات"
|
||||
}
|
||||
},
|
||||
"EVENTS": {
|
||||
"HEADER": {
|
||||
"TITLE": "Activities"
|
||||
"TITLE": "الأنشطة"
|
||||
},
|
||||
"BUTTON": {
|
||||
"PILL_BUTTON_NOTES": "notes",
|
||||
"PILL_BUTTON_EVENTS": "events",
|
||||
"PILL_BUTTON_NOTES": "ملاحظات",
|
||||
"PILL_BUTTON_EVENTS": "الأحداث",
|
||||
"PILL_BUTTON_CONVO": "المحادثات"
|
||||
}
|
||||
},
|
||||
"CUSTOM_ATTRIBUTES": {
|
||||
"TITLE": "سمات مخصصة",
|
||||
"BUTTON": "Add custom attribute",
|
||||
"BUTTON": "إضافة سمة خاصة",
|
||||
"NOT_AVAILABLE": "There are no custom attributes available for this contact.",
|
||||
"ADD": {
|
||||
"TITLE": "Create custom attribute",
|
||||
"DESC": "Add custom information to this contact."
|
||||
"TITLE": "إنشاء سمة خاصة",
|
||||
"DESC": "أضف معلومات خاصة إلى جهة الاتصال هذه."
|
||||
},
|
||||
"FORM": {
|
||||
"CREATE": "Add attribute",
|
||||
"CREATE": "أضف سمة",
|
||||
"CANCEL": "إلغاء",
|
||||
"NAME": {
|
||||
"LABEL": "Custom attribute name",
|
||||
"PLACEHOLDER": "Eg: shopify id",
|
||||
"ERROR": "Invalid custom attribute name"
|
||||
"LABEL": "اسم السمة الخاصة",
|
||||
"PLACEHOLDER": "مثال: shopify id",
|
||||
"ERROR": "اسم السمة المخصصة غير صالح"
|
||||
},
|
||||
"VALUE": {
|
||||
"LABEL": "Attribute value",
|
||||
"LABEL": "قيمة السمة",
|
||||
"PLACEHOLDER": "Eg: 11901 "
|
||||
}
|
||||
}
|
||||
},
|
||||
"MERGE_CONTACTS": {
|
||||
"TITLE": "Merge contacts",
|
||||
"DESCRIPTION": "Merge contact is helpful when you have duplicated entries of the same contact. Merging action takes a primary contact and a child contact. After merging, all details in the primary contact will remain the same. If the primary contact doesn't have a field, then the value from the child contact will be used after merging. If a conflict happens, fields in primary contact will remain unaffected, but fields from secondary will be copied to the custom attributes in the primary contact.",
|
||||
"TITLE": "دمج جهة الاتصال",
|
||||
"DESCRIPTION": "دمج جهة الاتصال مفيد عندما يكون لديك مدخلات مكررة لنفس جهة الاتصال. عملية الدمج تأخذ جهة اتصال رئيسية وتدمجها بجهة الاتصال المكررة. بعد الدمج، ستبقى جميع التفاصيل في جهة الاتصال الرئيسية كما هي. إذا لم يكن لدى جهة الاتصال الرئيسية حقل ، فسيتم استخدام القيمة من جهة الاتصال المكررة بعد الدمج. إذا حدث تضارب بالبيانات، ستبقى الحقول في جهة الاتصال الأساسية غير متأثرة، ولكن الحقول من جهة الاتصال الثانوية سيتم نسخها إلى السمات المخصصة في جهة الاتصال الرئيسية.",
|
||||
"PRIMARY": {
|
||||
"TITLE": "Primary contact"
|
||||
"TITLE": "جهة الاتصال الرئيسية"
|
||||
},
|
||||
"CHILD": {
|
||||
"TITLE": "Contact to merge",
|
||||
"PLACEHOLDER": "Choose a contact"
|
||||
"TITLE": "دمج جهة الإتصال",
|
||||
"PLACEHOLDER": "اختر جهة اتصال"
|
||||
},
|
||||
"SUMMARY": {
|
||||
"TITLE": "Summary",
|
||||
"DELETE_WARNING": "Contact of <strong>%{childContactName}</strong>will be deleted.",
|
||||
"ATTRIBUTE_WARNING": "Contact details of <strong>%{childContactName}</strong> will be copied to <strong>%{primaryContactName}</strong>."
|
||||
"TITLE": "ملخص",
|
||||
"DELETE_WARNING": "الاتصال بـ <strong>%{childContactName}</strong>سيتم حذفه.",
|
||||
"ATTRIBUTE_WARNING": "سيتم نسخ تفاصيل الاتصال بـ <strong>%{childContactName}</strong> إلى <strong>%{primaryContactName}</strong>."
|
||||
},
|
||||
"SEARCH": {
|
||||
"ERROR": "ERROR_MESSAGE"
|
||||
"ERROR": "رسالة_خطأ"
|
||||
},
|
||||
"FORM": {
|
||||
"SUBMIT": " Merge contacts",
|
||||
"SUBMIT": " دمج جهة الاتصال",
|
||||
"CANCEL": "إلغاء",
|
||||
"CHILD_CONTACT": {
|
||||
"ERROR": "Select a child contact to merge"
|
||||
"ERROR": "حدد جهة اتصال فرعية للدمج"
|
||||
},
|
||||
"SUCCESS_MESSAGE": "Contact merged successfully",
|
||||
"ERROR_MESSAGE": "Could not merge contcts, try again!"
|
||||
"SUCCESS_MESSAGE": "تم دمج جهة الاتصال بنجاح",
|
||||
"ERROR_MESSAGE": "تعذر دمج جهات الاتصال ، حاول مرة أخرى!"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
"SEARCH_MESSAGES": "البحث عن رسائل في المحادثات",
|
||||
"SEARCH": {
|
||||
"TITLE": "البحث في الرسائل",
|
||||
"RESULT_TITLE": "نتائج البحث",
|
||||
"LOADING_MESSAGE": "جار تجزئة البيانات...",
|
||||
"PLACEHOLDER": "اكتب أي نص للبحث في الرسائل",
|
||||
"NO_MATCHING_RESULTS": "لم يتم العثور على النتائج."
|
||||
|
@ -20,18 +21,18 @@
|
|||
"LOADING_CONVERSATIONS": "جاري تحميل المحادثات",
|
||||
"CANNOT_REPLY": "لا يمكنك الرد بسبب",
|
||||
"24_HOURS_WINDOW": "قيد نافذة الـ 24 ساعة",
|
||||
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
|
||||
"TWILIO_WHATSAPP_CAN_REPLY": "يمكنك فقط الرد على هذه المحادثة باستخدام رسالة قالب بسبب",
|
||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "قيد نافذة الـ 24 ساعة",
|
||||
"LAST_INCOMING_TWEET": "أنت ترد على آخر تغريدة واردة",
|
||||
"SELECT_A_TWEET_TO_REPLY": "الرجاء تحديد تغريدة للرد عليها.",
|
||||
"REPLYING_TO": "أنت ترد على:",
|
||||
"REMOVE_SELECTION": "إزالة التحديد",
|
||||
"DOWNLOAD": "تنزيل",
|
||||
"UPLOADING_ATTACHMENTS": "جاري تحميل المرفقات...",
|
||||
"SUCCESS_DELETE_MESSAGE": "Message deleted successfully",
|
||||
"FAIL_DELETE_MESSSAGE": "Couldn't delete message! Try again",
|
||||
"SUCCESS_DELETE_MESSAGE": "تم حذف الرسالة بنجاح",
|
||||
"FAIL_DELETE_MESSSAGE": "تعذر حذف الرسالة! حاول مرة أخرى",
|
||||
"NO_RESPONSE": "لا توجد استجابة",
|
||||
"RATING_TITLE": "Rating",
|
||||
"FEEDBACK_TITLE": "Feedback",
|
||||
"RATING_TITLE": "التقييم",
|
||||
"FEEDBACK_TITLE": "الملاحظات",
|
||||
"HEADER": {
|
||||
"RESOLVE_ACTION": "إغلاق المحادثة",
|
||||
"REOPEN_ACTION": "إعادة فتح",
|
||||
|
@ -41,7 +42,13 @@
|
|||
"DETAILS": "التفاصيل"
|
||||
},
|
||||
"RESOLVE_DROPDOWN": {
|
||||
"OPEN_BOT": "Open with bot"
|
||||
"MARK_PENDING": "تحديد كمعلق",
|
||||
"SNOOZE": {
|
||||
"TITLE": "تأجيل حتى",
|
||||
"NEXT_REPLY": "الرد القادم",
|
||||
"TOMORROW": "غداً",
|
||||
"NEXT_WEEK": "الأسبوع المقبل"
|
||||
}
|
||||
},
|
||||
"FOOTER": {
|
||||
"MSG_INPUT": "زر Shift + Enter لإضافة سطر جديد. ابدأ بزر / للاختيار من الردود السريعة.",
|
||||
|
@ -57,13 +64,26 @@
|
|||
"TIP_EMOJI_ICON": "إظهار قائمة الرموز التعبيرية",
|
||||
"TIP_ATTACH_ICON": "إرفاق الملفات",
|
||||
"ENTER_TO_SEND": "زر الإدخل للإرسال",
|
||||
"DRAG_DROP": "Drag and drop here to attach"
|
||||
"DRAG_DROP": "اسحب و أسقط هنا للإرفاق",
|
||||
"EMAIL_HEAD": {
|
||||
"ADD_BCC": "إضافة bcc",
|
||||
"CC": {
|
||||
"LABEL": "CC",
|
||||
"PLACEHOLDER": "البريد الإلكتروني مفصولة بفاصلة",
|
||||
"ERROR": "الرجاء إدخال عنوان بريد إلكتروني صحيح"
|
||||
},
|
||||
"BCC": {
|
||||
"LABEL": "BCC",
|
||||
"PLACEHOLDER": "البريد الإلكتروني مفصولة بفاصلة",
|
||||
"ERROR": "الرجاء إدخال عنوان بريد إلكتروني صحيح"
|
||||
}
|
||||
}
|
||||
},
|
||||
"VISIBLE_TO_AGENTS": "ملاحظة خاصة: مرئية فقط لأعضاء فريق العمل والموظفين",
|
||||
"CHANGE_STATUS": "تم تغيير حالة المحادثة",
|
||||
"CHANGE_AGENT": "تم تغيير الموظف الذي تم إحالة المحادثة إليه",
|
||||
"CHANGE_TEAM": "تم تغيير فريق المحادثة",
|
||||
"FILE_SIZE_LIMIT": "File exceeds the {MAXIMUM_FILE_UPLOAD_SIZE} attachment limit",
|
||||
"FILE_SIZE_LIMIT": "حجم الملف يتجاوز حد الاقصى وهو {MAXIMUM_FILE_UPLOAD_SIZE}",
|
||||
"SENT_BY": "أرسلت بواسطة:",
|
||||
"ASSIGNMENT": {
|
||||
"SELECT_AGENT": "اختر وكيل",
|
||||
|
@ -118,10 +138,24 @@
|
|||
},
|
||||
"CONVERSATION_SIDEBAR": {
|
||||
"ASSIGNEE_LABEL": "الوكيل المكلف",
|
||||
"SELF_ASSIGN": "Assign to me",
|
||||
"SELF_ASSIGN": "إسناد لي",
|
||||
"TEAM_LABEL": "العضو المكلف",
|
||||
"SELECT": {
|
||||
"PLACEHOLDER": "لا شيء"
|
||||
},
|
||||
"ACCORDION": {
|
||||
"CONTACT_DETAILS": "Contact Details",
|
||||
"CONVERSATION_ACTIONS": "Conversation Actions",
|
||||
"CONVERSATION_LABELS": "وسوم المحادثة",
|
||||
"CONVERSATION_INFO": "Conversation Information",
|
||||
"CONTACT_ATTRIBUTES": "Contact Attributes",
|
||||
"PREVIOUS_CONVERSATION": "المحادثات السابقة"
|
||||
}
|
||||
},
|
||||
"EMAIL_HEADER": {
|
||||
"TO": "إلى",
|
||||
"BCC": "Bcc",
|
||||
"CC": "Cc",
|
||||
"SUBJECT": "الموضوع"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"CSAT": {
|
||||
"TITLE": "Rate your conversation",
|
||||
"PLACEHOLDER": "Tell us more..."
|
||||
"TITLE": "قيم محادثتك",
|
||||
"PLACEHOLDER": "أخبرنا المزيد..."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
"ADD": {
|
||||
"CHANNEL_NAME": {
|
||||
"LABEL": "اسم صندوق الوارد لقناة التواصل",
|
||||
"PLACEHOLDER": "Enter your inbox name (eg: Acme Inc)"
|
||||
"PLACEHOLDER": "أدخل اسم صندوق الوارد الخاص بك (مثال: Acme Inc)"
|
||||
},
|
||||
"WEBSITE_NAME": {
|
||||
"LABEL": "اسم الموقع",
|
||||
|
@ -128,12 +128,12 @@
|
|||
}
|
||||
},
|
||||
"SMS": {
|
||||
"TITLE": "SMS Channel via Twilio",
|
||||
"DESC": "Start supporting your customers via SMS with Twilio integration."
|
||||
"TITLE": "قناة SMS عبر Twilio",
|
||||
"DESC": "ابدأ في دعم عملائك عبر الرسائل القصيرة بإستخدام Twilio."
|
||||
},
|
||||
"WHATSAPP": {
|
||||
"TITLE": "Whatsapp Channel via Twilio",
|
||||
"DESC": "Start supporting your customers via Whatsapp with Twilio integration."
|
||||
"TITLE": "قناة Whatsapp عبر Twilio",
|
||||
"DESC": "ابدأ في دعم عملائك عبر الواتساب بإستخدام Twilio."
|
||||
},
|
||||
"API_CHANNEL": {
|
||||
"TITLE": "قناة API",
|
||||
|
@ -172,9 +172,47 @@
|
|||
},
|
||||
"FINISH_MESSAGE": "بدء إعادة توجيه رسائل البريد الإلكتروني الخاصة بك إلى عنوان البريد الإلكتروني التالي."
|
||||
},
|
||||
"LINE_CHANNEL": {
|
||||
"TITLE": "LINE Channel",
|
||||
"DESC": "Integrate with LINE channel and start supporting your customers.",
|
||||
"CHANNEL_NAME": {
|
||||
"LABEL": "اسم القناة",
|
||||
"PLACEHOLDER": "الرجاء إدخال اسم القناة",
|
||||
"ERROR": "هذا الحقل مطلوب"
|
||||
},
|
||||
"LINE_CHANNEL_ID": {
|
||||
"LABEL": "LINE Channel ID",
|
||||
"PLACEHOLDER": "LINE Channel ID"
|
||||
},
|
||||
"LINE_CHANNEL_SECRET": {
|
||||
"LABEL": "LINE Channel Secret",
|
||||
"PLACEHOLDER": "LINE Channel Secret"
|
||||
},
|
||||
"LINE_CHANNEL_TOKEN": {
|
||||
"LABEL": "LINE Channel Token",
|
||||
"PLACEHOLDER": "LINE Channel Token"
|
||||
},
|
||||
"SUBMIT_BUTTON": "Create LINE Channel",
|
||||
"API": {
|
||||
"ERROR_MESSAGE": "We were not able to save the LINE channel"
|
||||
}
|
||||
},
|
||||
"TELEGRAM_CHANNEL": {
|
||||
"TITLE": "Telegram Channel",
|
||||
"DESC": "Integrate with Telegram channel and start supporting your customers.",
|
||||
"BOT_TOKEN": {
|
||||
"LABEL": "Bot Token",
|
||||
"SUBTITLE": "Configure the bot token you have obtained from Telegram BotFather.",
|
||||
"PLACEHOLDER": "Bot Token"
|
||||
},
|
||||
"SUBMIT_BUTTON": "Create Telegram Channel",
|
||||
"API": {
|
||||
"ERROR_MESSAGE": "We were not able to save the telegram channel"
|
||||
}
|
||||
},
|
||||
"AUTH": {
|
||||
"TITLE": "Choose a channel",
|
||||
"DESC": "Chatwoot supports live-chat widget, Facebook page, Twitter profile, Whatsapp, Email etc., as channels. If you want to build a custom channel, you can create it using the API channel. Select one channel from the options below to proceed."
|
||||
"TITLE": "اختر قناة",
|
||||
"DESC": "شاتوت يدعم أداة الدردشة المباشرة، صفحة الفيسبوك، ملف تويتر الشخصي، واتسب، البريد الإلكتروني وما إلى ذلك، كقنوات. إذا كنت ترغب في إنشاء قناة مخصصة، يمكنك إنشاءها باستخدام قناة API. حدد قناة واحدة من الخيارات أدناه للمتابعة."
|
||||
},
|
||||
"AGENTS": {
|
||||
"TITLE": "موظف الدعم",
|
||||
|
@ -232,6 +270,7 @@
|
|||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "حذف",
|
||||
"AVATAR_DELETE_BUTTON_TEXT": "Delete Avatar",
|
||||
"CONFIRM": {
|
||||
"TITLE": "تأكيد الحذف",
|
||||
"MESSAGE": "هل أنت متأكد من الحذف ",
|
||||
|
@ -241,14 +280,16 @@
|
|||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "تم حذف قناة التواصل بنجاح",
|
||||
"ERROR_MESSAGE": "تعذر حذف قناة التواصل. الرجاء المحاولة مرة أخرى لاحقاً."
|
||||
"ERROR_MESSAGE": "تعذر حذف قناة التواصل. الرجاء المحاولة مرة أخرى لاحقاً.",
|
||||
"AVATAR_SUCCESS_MESSAGE": "Inbox avatar deleted successfully",
|
||||
"AVATAR_ERROR_MESSAGE": "Could not delete the inbox avatar. Please try again later."
|
||||
}
|
||||
},
|
||||
"TABS": {
|
||||
"SETTINGS": "الإعدادات",
|
||||
"COLLABORATORS": "المتعاونون",
|
||||
"CONFIGURATION": "الإعدادات",
|
||||
"CAMPAIGN": "Campaigns",
|
||||
"CAMPAIGN": "الحملات",
|
||||
"PRE_CHAT_FORM": "نموذج ما قبل الدردشة",
|
||||
"BUSINESS_HOURS": "ساعات العمل"
|
||||
},
|
||||
|
@ -273,7 +314,11 @@
|
|||
"INBOX_UPDATE_SUB_TEXT": "تحديث إعدادات قناة التواصل",
|
||||
"AUTO_ASSIGNMENT_SUB_TEXT": "تمكين أو تعطيل الإسناد التلقائي للمحادثات الجديدة إلى الموظفين المضافين إلى قناة التواصل هذه.",
|
||||
"HMAC_VERIFICATION": "التحقق من هوية المستخدم",
|
||||
"HMAC_DESCRIPTION": "للتحقق من هوية المستخدمين، يسمح لك SDK بتمرير 'identifier_hash' لكل مستخدم. يمكنك إنشاء HMAC باستخدام 'sha256' مع المفتاح المعروض هنا."
|
||||
"HMAC_DESCRIPTION": "Inorder to validate the user's identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here.",
|
||||
"INBOX_IDENTIFIER": "Inbox Identifier",
|
||||
"INBOX_IDENTIFIER_SUB_TEXT": "Use the `inbox_identifier` token shown here to authentication your API clients.",
|
||||
"FORWARD_EMAIL_TITLE": "Forward to Email",
|
||||
"FORWARD_EMAIL_SUB_TEXT": "بدء إعادة توجيه رسائل البريد الإلكتروني الخاصة بك إلى عنوان البريد الإلكتروني التالي."
|
||||
},
|
||||
"FACEBOOK_REAUTHORIZE": {
|
||||
"TITLE": "إعادة التصريح",
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import { default as _agentMgmt } from './agentMgmt.json';
|
||||
import { default as _attributesMgmt } from './attributesMgmt.json';
|
||||
import { default as _campaign } from './campaign.json';
|
||||
import { default as _cannedMgmt } from './cannedMgmt.json';
|
||||
import { default as _chatlist } from './chatlist.json';
|
||||
import { default as _contact } from './contact.json';
|
||||
import { default as _conversation } from './conversation.json';
|
||||
import { default as _csatMgmtMgmt } from './csatMgmt.json';
|
||||
import { default as _generalSettings } from './generalSettings.json';
|
||||
import { default as _inboxMgmt } from './inboxMgmt.json';
|
||||
import { default as _integrationApps } from './integrationApps.json';
|
||||
import { default as _integrations } from './integrations.json';
|
||||
import { default as _labelsMgmt } from './labelsMgmt.json';
|
||||
import { default as _login } from './login.json';
|
||||
|
@ -18,13 +21,16 @@ import { default as _teamsSettings } from './teamsSettings.json';
|
|||
|
||||
export default {
|
||||
..._agentMgmt,
|
||||
..._attributesMgmt,
|
||||
..._campaign,
|
||||
..._cannedMgmt,
|
||||
..._chatlist,
|
||||
..._contact,
|
||||
..._conversation,
|
||||
..._csatMgmtMgmt,
|
||||
..._generalSettings,
|
||||
..._inboxMgmt,
|
||||
..._integrationApps,
|
||||
..._integrations,
|
||||
..._labelsMgmt,
|
||||
..._login,
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
},
|
||||
"LIST": {
|
||||
"FETCHING": "Fetching integration hooks",
|
||||
"INBOX": "Inbox",
|
||||
"INBOX": "صندوق الوارد",
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "حذف"
|
||||
}
|
||||
|
|
|
@ -49,41 +49,41 @@
|
|||
},
|
||||
{
|
||||
"id": 4,
|
||||
"name": "Last year"
|
||||
"name": "العام الماضي"
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"name": "Custom date range"
|
||||
"name": "تحديد نطاق المدة"
|
||||
}
|
||||
],
|
||||
"CUSTOM_DATE_RANGE": {
|
||||
"CONFIRM": "Apply",
|
||||
"PLACEHOLDER": "Select date range"
|
||||
"CONFIRM": "تطبيق",
|
||||
"PLACEHOLDER": "اختر نطاق المدة"
|
||||
}
|
||||
},
|
||||
"CSAT_REPORTS": {
|
||||
"HEADER": "CSAT Reports",
|
||||
"NO_RECORDS": "There are no CSAT survey responses available.",
|
||||
"HEADER": "تقارير CSAT",
|
||||
"NO_RECORDS": "لا توجد ردود متوفرة على الدراسة الاستقصائية CSAT.",
|
||||
"TABLE": {
|
||||
"HEADER": {
|
||||
"CONTACT_NAME": "Contact",
|
||||
"AGENT_NAME": "Assigned agent",
|
||||
"RATING": "Rating",
|
||||
"FEEDBACK_TEXT": "Feedback comment"
|
||||
"CONTACT_NAME": "جهات الاتصال",
|
||||
"AGENT_NAME": "الوكيل المكلف",
|
||||
"RATING": "التقييم",
|
||||
"FEEDBACK_TEXT": "تعليق الملاحظات"
|
||||
}
|
||||
},
|
||||
"METRIC": {
|
||||
"TOTAL_RESPONSES": {
|
||||
"LABEL": "Total responses",
|
||||
"TOOLTIP": "Total number of responses collected"
|
||||
"LABEL": "إجمالي الردود",
|
||||
"TOOLTIP": "العدد الإجمالي للردود التي تم جمعها"
|
||||
},
|
||||
"SATISFACTION_SCORE": {
|
||||
"LABEL": "Satisfaction score",
|
||||
"TOOLTIP": "Total number of positive responses / Total number of responses * 100"
|
||||
"LABEL": "درجة الرضا",
|
||||
"TOOLTIP": "العدد الإجمالي للردود الإيجابية/ العدد الإجمالي للردود * 100"
|
||||
},
|
||||
"RESPONSE_RATE": {
|
||||
"LABEL": "Response rate",
|
||||
"TOOLTIP": "Total number of responses / Total number of CSAT survey messages sent * 100"
|
||||
"LABEL": "معدل الاستجابة",
|
||||
"TOOLTIP": "العدد الإجمالي للردود / العدد الإجمالي لرسائل الاستقصاء التي أرسلتها CSAT * 100"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,8 +29,8 @@
|
|||
"TITLE": "الإشعارات الصوتية",
|
||||
"NOTE": "تمكين التنبيهات الصوتية في لوحة التحكم للرسائل والمحادثات الجديدة.",
|
||||
"NONE": "لا شيء",
|
||||
"ASSIGNED": "Assigned Conversations",
|
||||
"ALL_CONVERSATIONS": "All Conversations"
|
||||
"ASSIGNED": "المحادثات المسندة",
|
||||
"ALL_CONVERSATIONS": "كل المحادثات"
|
||||
},
|
||||
"EMAIL_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "إشعارات البريد الإلكتروني",
|
||||
|
@ -81,9 +81,9 @@
|
|||
"PLACEHOLDER": "الرجاء إدخال عنوان البريد الإلكتروني الخاص بك، سيتم عرضه في المحادثات"
|
||||
},
|
||||
"CURRENT_PASSWORD": {
|
||||
"LABEL": "Current password",
|
||||
"ERROR": "Please enter the current password",
|
||||
"PLACEHOLDER": "Please enter the current password"
|
||||
"LABEL": "كلمة المرور الحالية",
|
||||
"ERROR": "الرجاء إدخال كلمة مرور جديدة",
|
||||
"PLACEHOLDER": "الرجاء إدخال كلمة مرور جديدة"
|
||||
},
|
||||
"PASSWORD": {
|
||||
"LABEL": "كلمة المرور",
|
||||
|
@ -102,6 +102,7 @@
|
|||
"CHANGE_ACCOUNTS": "تبديل الحساب",
|
||||
"SELECTOR_SUBTITLE": "اختر حساباً من القائمة التالية",
|
||||
"PROFILE_SETTINGS": "إعدادات الملف الشخصي",
|
||||
"KEYBOARD_SHORTCUTS": "اختصارات لوحة المفاتيح",
|
||||
"LOGOUT": "تسجيل الخروج"
|
||||
},
|
||||
"APP_GLOBAL": {
|
||||
|
@ -130,8 +131,8 @@
|
|||
"SIDEBAR": {
|
||||
"CONVERSATIONS": "المحادثات",
|
||||
"REPORTS": "التقارير",
|
||||
"CONTACTS": "جهات الاتصال",
|
||||
"SETTINGS": "الإعدادات",
|
||||
"CONTACTS": "جهات الاتصال",
|
||||
"HOME": "الرئيسية",
|
||||
"AGENTS": "موظف الدعم",
|
||||
"INBOXES": "قنوات التواصل",
|
||||
|
@ -139,15 +140,20 @@
|
|||
"CANNED_RESPONSES": "الردود السريعة",
|
||||
"INTEGRATIONS": "خيارات الربط",
|
||||
"ACCOUNT_SETTINGS": "إعدادات الحساب",
|
||||
"APPLICATIONS": "Applications",
|
||||
"APPLICATIONS": "التطبيقات",
|
||||
"LABELS": "الوسوم",
|
||||
"ATTRIBUTES": "السمات",
|
||||
"TEAMS": "الفرق",
|
||||
"ALL_CONTACTS": "All Contacts",
|
||||
"ALL_CONTACTS": "جميع جهات الاتصال",
|
||||
"TAGGED_WITH": "Tagged with",
|
||||
"REPORTS_OVERVIEW": "Overview",
|
||||
"CSAT": "CSAT"
|
||||
"REPORTS_OVERVIEW": "نظرة عامة",
|
||||
"CSAT": "CSAT",
|
||||
"CAMPAIGNS": "الحملات",
|
||||
"ONGOING": "جارية",
|
||||
"ONE_OFF": "إيقاف واحد"
|
||||
},
|
||||
"CREATE_ACCOUNT": {
|
||||
"NO_ACCOUNT_WARNING": "أوه! لم نتمكن من العثور على الحساب. الرجاء إنشاء حساب جديد للمتابعة.",
|
||||
"NEW_ACCOUNT": "حساب جديد",
|
||||
"SELECTOR_SUBTITLE": "إنشاء حساب جديد",
|
||||
"API": {
|
||||
|
@ -162,5 +168,30 @@
|
|||
},
|
||||
"SUBMIT": "إرسال"
|
||||
}
|
||||
},
|
||||
"KEYBOARD_SHORTCUTS": {
|
||||
"TITLE": {
|
||||
"OPEN_CONVERSATION": "فتح المحادثة",
|
||||
"RESOLVE_AND_NEXT": "حل وانتقل إلى التالي",
|
||||
"NAVIGATE_DROPDOWN": "تصفح العناصر المنسدلة",
|
||||
"RESOLVE_CONVERSATION": "إعادة فتح المحادثة",
|
||||
"GO_TO_CONVERSATION_DASHBOARD": "الذهاب إلى لوحة المحادثة",
|
||||
"ADD_ATTACHMENT": "إضافة مرفقات",
|
||||
"GO_TO_CONTACTS_DASHBOARD": "الذهاب إلى لوحة جهات الاتصال",
|
||||
"TOGGLE_SIDEBAR": "تبديل الشريط الجانبي",
|
||||
"GO_TO_REPORTS_SIDEBAR": "الذهاب إلى شريط التقارير الجانبي",
|
||||
"MOVE_TO_NEXT_TAB": "نقل إلى علامة التبويب التالية في قائمة المحادثات",
|
||||
"GO_TO_SETTINGS": "انتقل إلى الإعدادات",
|
||||
"SWITCH_CONVERSATION_STATUS": "Switch to the next conversation status",
|
||||
"SWITCH_TO_PRIVATE_NOTE": "Switch to Private Note",
|
||||
"TOGGLE_RICH_CONTENT_EDITOR": "Toggle Rich Content editor",
|
||||
"SWITCH_TO_REPLY": "Switch to Reply",
|
||||
"TOGGLE_SNOOZE_DROPDOWN": "Toggle snooze dropdown"
|
||||
},
|
||||
"KEYS": {
|
||||
"WINDOWS_KEY_AND_COMMAND_KEY": "Win / ⌘",
|
||||
"ALT_OR_OPTION_KEY": "Alt / ⌥",
|
||||
"FORWARD_SLASH_KEY": "/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,6 +91,23 @@
|
|||
},
|
||||
"SEARCH": {
|
||||
"NO_RESULTS": "No results found."
|
||||
},
|
||||
"MULTI_SELECTOR": {
|
||||
"PLACEHOLDER": "None",
|
||||
"TITLE": {
|
||||
"AGENT": "Select agent",
|
||||
"TEAM": "Select team"
|
||||
},
|
||||
"SEARCH": {
|
||||
"NO_RESULTS": {
|
||||
"AGENT": "No s'han trobat agents",
|
||||
"TEAM": "No teams found"
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"AGENT": "Search agents",
|
||||
"TEAM": "Search teams"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
85
app/javascript/dashboard/i18n/locale/ca/attributesMgmt.json
Normal file
85
app/javascript/dashboard/i18n/locale/ca/attributesMgmt.json
Normal file
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"ATTRIBUTES_MGMT": {
|
||||
"HEADER": "Attributes",
|
||||
"HEADER_BTN_TXT": "Add Attribute",
|
||||
"LOADING": "Fetching attributes",
|
||||
"SIDEBAR_TXT": "<p><b>Attributes</b> <p>A custom attribute tracks facts about your contacts/conversation — like the subscription plan, or when they ordered the first item etc. <br /><br />For creating a Attributes, just click on the <b>Add Attribute.</b> You can also edit or delete an existing Attribute by clicking on the Edit or Delete button.</p>",
|
||||
"ADD": {
|
||||
"TITLE": "Add attribute",
|
||||
"SUBMIT": "Crear",
|
||||
"CANCEL_BUTTON_TEXT": "Cancel·la",
|
||||
"FORM": {
|
||||
"NAME": {
|
||||
"LABEL": "Display Name",
|
||||
"PLACEHOLDER": "Enter attribute display name",
|
||||
"ERROR": "Name is required"
|
||||
},
|
||||
"DESC": {
|
||||
"LABEL": "Descripció",
|
||||
"PLACEHOLDER": "Enter attribute description",
|
||||
"ERROR": "Description is required"
|
||||
},
|
||||
"MODEL": {
|
||||
"LABEL": "Model",
|
||||
"PLACEHOLDER": "Please select a model",
|
||||
"ERROR": "Model is required"
|
||||
},
|
||||
"TYPE": {
|
||||
"LABEL": "Type",
|
||||
"PLACEHOLDER": "Selecciona un tipus",
|
||||
"ERROR": "Type is required"
|
||||
},
|
||||
"KEY": {
|
||||
"LABEL": "Key"
|
||||
}
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Attribute added successfully",
|
||||
"ERROR_MESSAGE": "Could not able to create an attribute, Please try again later"
|
||||
}
|
||||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Esborrar",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Attribute deleted successfully.",
|
||||
"ERROR_MESSAGE": "Couldn't delete the attribute. Try again."
|
||||
},
|
||||
"CONFIRM": {
|
||||
"TITLE": "Are you sure want to delete - %{attributeName}",
|
||||
"PLACE_HOLDER": "Please type {attributeName} to confirm",
|
||||
"MESSAGE": "Deleting will remove the attribute",
|
||||
"YES": "Suprimeix ",
|
||||
"NO": "Cancel·la"
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"TITLE": "Edit attribute",
|
||||
"UPDATE_BUTTON_TEXT": "Actualitza",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Attribute updated successfully",
|
||||
"ERROR_MESSAGE": "There was an error updating attribute, please try again"
|
||||
}
|
||||
},
|
||||
"TABS": {
|
||||
"HEADER": "Atributs personalitzats",
|
||||
"CONVERSATION": "Conversation",
|
||||
"CONTACT": "Contact"
|
||||
},
|
||||
"LIST": {
|
||||
"TABLE_HEADER": [
|
||||
"Nom",
|
||||
"Descripció",
|
||||
"Type",
|
||||
"Key"
|
||||
],
|
||||
"BUTTONS": {
|
||||
"EDIT": "Edita",
|
||||
"DELETE": "Esborrar"
|
||||
},
|
||||
"EMPTY_RESULT": {
|
||||
"404": "There are no attributes created",
|
||||
"NOT_FOUND": "There are no attributes configured"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,7 +2,10 @@
|
|||
"CAMPAIGN": {
|
||||
"HEADER": "Campaigns",
|
||||
"SIDEBAR_TXT": "Proactive messages allow the customer to send outbound messages to their contacts which would trigger more conversations. Click on <b>Add Campaign</b> to create a new campaign. You can also edit or delete an existing campaign by clicking on the Edit or Delete button.",
|
||||
"HEADER_BTN_TXT": "Create a campaign",
|
||||
"HEADER_BTN_TXT": {
|
||||
"ONE_OFF": "Create a one off campaign",
|
||||
"ONGOING": "Create a ongoing campaign"
|
||||
},
|
||||
"ADD": {
|
||||
"TITLE": "Create a campaign",
|
||||
"DESC": "Proactive messages allow the customer to send outbound messages to their contacts which would trigger more conversations.",
|
||||
|
@ -25,6 +28,11 @@
|
|||
"PLACEHOLDER": "Select the customer labels",
|
||||
"ERROR": "Audience is required"
|
||||
},
|
||||
"INBOX": {
|
||||
"LABEL": "Select Inbox",
|
||||
"PLACEHOLDER": "Select Inbox",
|
||||
"ERROR": "Inbox is required"
|
||||
},
|
||||
"MESSAGE": {
|
||||
"LABEL": "Missatge",
|
||||
"PLACEHOLDER": "Please enter the message of campaign",
|
||||
|
@ -80,6 +88,7 @@
|
|||
"TABLE_HEADER": {
|
||||
"TITLE": "Title",
|
||||
"MESSAGE": "Missatge",
|
||||
"INBOX": "Inbox",
|
||||
"STATUS": "Estat",
|
||||
"SENDER": "Sender",
|
||||
"URL": "URL",
|
||||
|
@ -101,6 +110,16 @@
|
|||
"SENDER": {
|
||||
"BOT": "Bot"
|
||||
}
|
||||
},
|
||||
"ONE_OFF": {
|
||||
"HEADER": "One off campaigns",
|
||||
"404": "There are no one off campaigns created",
|
||||
"INBOXES_NOT_FOUND": "Please create an sms inbox and start adding campaigns"
|
||||
},
|
||||
"ONGOING": {
|
||||
"HEADER": "Ongoing campaigns",
|
||||
"404": "There are no ongoing campaigns created",
|
||||
"INBOXES_NOT_FOUND": "Please create an website inbox and start adding campaigns"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,8 +47,12 @@
|
|||
"VALUE": "resolved"
|
||||
},
|
||||
{
|
||||
"TEXT": "Bot",
|
||||
"VALUE": "bot"
|
||||
"TEXT": "Pending",
|
||||
"VALUE": "pending"
|
||||
},
|
||||
{
|
||||
"TEXT": "Snoozed",
|
||||
"VALUE": "snoozed"
|
||||
}
|
||||
],
|
||||
"ATTACHMENTS": {
|
||||
|
|
|
@ -37,7 +37,12 @@
|
|||
"MUTED_SUCCESS": "Aquesta conversa s'ha silenciat durant 6 hores",
|
||||
"UNMUTED_SUCCESS": "La conversa te desactivat el silenci",
|
||||
"SEND_TRANSCRIPT": "Envia la transcripció",
|
||||
"EDIT_LABEL": "Edita"
|
||||
"EDIT_LABEL": "Edita",
|
||||
"SIDEBAR_SECTIONS": {
|
||||
"CUSTOM_ATTRIBUTES": "Atributs personalitzats",
|
||||
"CONTACT_LABELS": "Contact Labels",
|
||||
"PREVIOUS_CONVERSATIONS": "Converses prèvies"
|
||||
}
|
||||
},
|
||||
"EDIT_CONTACT": {
|
||||
"BUTTON_LABEL": "Edita el contacte",
|
||||
|
@ -186,8 +191,8 @@
|
|||
}
|
||||
},
|
||||
"CUSTOM_ATTRIBUTES": {
|
||||
"TITLE": "Atributs personalitzats",
|
||||
"BUTTON": "Add custom attribute",
|
||||
"NOT_AVAILABLE": "There are no custom attributes available for this contact.",
|
||||
"ADD": {
|
||||
"TITLE": "Create custom attribute",
|
||||
"DESC": "Add custom information to this contact."
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
"SEARCH_MESSAGES": "Cerca missatges a les converses",
|
||||
"SEARCH": {
|
||||
"TITLE": "Cerca missatges",
|
||||
"RESULT_TITLE": "Search Results",
|
||||
"LOADING_MESSAGE": "S'estan restringint les dades...",
|
||||
"PLACEHOLDER": "Escriu qualsevol text per cercar missatges",
|
||||
"NO_MATCHING_RESULTS": "No results found."
|
||||
|
@ -22,7 +23,7 @@
|
|||
"24_HOURS_WINDOW": "Restricció de finestra de missatges de 24 hores",
|
||||
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
|
||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "Restricció de finestra de missatges de 24 hores",
|
||||
"LAST_INCOMING_TWEET": "Estas responent a l'últim tuit entrant",
|
||||
"SELECT_A_TWEET_TO_REPLY": "Please select a tweet to reply to.",
|
||||
"REPLYING_TO": "Estas responent a:",
|
||||
"REMOVE_SELECTION": "Elimina la selecció",
|
||||
"DOWNLOAD": "Descarrega",
|
||||
|
@ -41,7 +42,13 @@
|
|||
"DETAILS": "detalls"
|
||||
},
|
||||
"RESOLVE_DROPDOWN": {
|
||||
"OPEN_BOT": "Open with bot"
|
||||
"MARK_PENDING": "Mark as pending",
|
||||
"SNOOZE": {
|
||||
"TITLE": "Snooze until",
|
||||
"NEXT_REPLY": "Next reply",
|
||||
"TOMORROW": "Tomorrow",
|
||||
"NEXT_WEEK": "Next week"
|
||||
}
|
||||
},
|
||||
"FOOTER": {
|
||||
"MSG_INPUT": "Shift + enter per a una línia nova. Comença amb '/' per seleccionar una resposta predeterminada.",
|
||||
|
@ -57,7 +64,20 @@
|
|||
"TIP_EMOJI_ICON": "Mostra la selecció d'emoticones",
|
||||
"TIP_ATTACH_ICON": "Ajuntar fitxers",
|
||||
"ENTER_TO_SEND": "Intro per enviar",
|
||||
"DRAG_DROP": "Drag and drop here to attach"
|
||||
"DRAG_DROP": "Drag and drop here to attach",
|
||||
"EMAIL_HEAD": {
|
||||
"ADD_BCC": "Add bcc",
|
||||
"CC": {
|
||||
"LABEL": "CC",
|
||||
"PLACEHOLDER": "Emails separated by commas",
|
||||
"ERROR": "Please enter valid email addresses"
|
||||
},
|
||||
"BCC": {
|
||||
"LABEL": "BCC",
|
||||
"PLACEHOLDER": "Emails separated by commas",
|
||||
"ERROR": "Please enter valid email addresses"
|
||||
}
|
||||
}
|
||||
},
|
||||
"VISIBLE_TO_AGENTS": "Nota privada: Només és visible per tu i el vostre equip",
|
||||
"CHANGE_STATUS": "Estat de la conversa canviat",
|
||||
|
@ -122,6 +142,20 @@
|
|||
"TEAM_LABEL": "Assigned Team",
|
||||
"SELECT": {
|
||||
"PLACEHOLDER": "None"
|
||||
},
|
||||
"ACCORDION": {
|
||||
"CONTACT_DETAILS": "Contact Details",
|
||||
"CONVERSATION_ACTIONS": "Conversation Actions",
|
||||
"CONVERSATION_LABELS": "Etiquetes de converses",
|
||||
"CONVERSATION_INFO": "Conversation Information",
|
||||
"CONTACT_ATTRIBUTES": "Contact Attributes",
|
||||
"PREVIOUS_CONVERSATION": "Converses prèvies"
|
||||
}
|
||||
},
|
||||
"EMAIL_HEADER": {
|
||||
"TO": "To",
|
||||
"BCC": "Bcc",
|
||||
"CC": "Cc",
|
||||
"SUBJECT": "Subject"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -172,6 +172,44 @@
|
|||
},
|
||||
"FINISH_MESSAGE": "Comença a reenviar els teus correus electrònics a la següent adreça electrònica."
|
||||
},
|
||||
"LINE_CHANNEL": {
|
||||
"TITLE": "LINE Channel",
|
||||
"DESC": "Integrate with LINE channel and start supporting your customers.",
|
||||
"CHANNEL_NAME": {
|
||||
"LABEL": "Nom del canal",
|
||||
"PLACEHOLDER": "Introduïu el nom del canal",
|
||||
"ERROR": "Aquest camp és obligatori"
|
||||
},
|
||||
"LINE_CHANNEL_ID": {
|
||||
"LABEL": "LINE Channel ID",
|
||||
"PLACEHOLDER": "LINE Channel ID"
|
||||
},
|
||||
"LINE_CHANNEL_SECRET": {
|
||||
"LABEL": "LINE Channel Secret",
|
||||
"PLACEHOLDER": "LINE Channel Secret"
|
||||
},
|
||||
"LINE_CHANNEL_TOKEN": {
|
||||
"LABEL": "LINE Channel Token",
|
||||
"PLACEHOLDER": "LINE Channel Token"
|
||||
},
|
||||
"SUBMIT_BUTTON": "Create LINE Channel",
|
||||
"API": {
|
||||
"ERROR_MESSAGE": "We were not able to save the LINE channel"
|
||||
}
|
||||
},
|
||||
"TELEGRAM_CHANNEL": {
|
||||
"TITLE": "Telegram Channel",
|
||||
"DESC": "Integrate with Telegram channel and start supporting your customers.",
|
||||
"BOT_TOKEN": {
|
||||
"LABEL": "Bot Token",
|
||||
"SUBTITLE": "Configure the bot token you have obtained from Telegram BotFather.",
|
||||
"PLACEHOLDER": "Bot Token"
|
||||
},
|
||||
"SUBMIT_BUTTON": "Create Telegram Channel",
|
||||
"API": {
|
||||
"ERROR_MESSAGE": "We were not able to save the telegram channel"
|
||||
}
|
||||
},
|
||||
"AUTH": {
|
||||
"TITLE": "Choose a channel",
|
||||
"DESC": "Chatwoot supports live-chat widget, Facebook page, Twitter profile, Whatsapp, Email etc., as channels. If you want to build a custom channel, you can create it using the API channel. Select one channel from the options below to proceed."
|
||||
|
@ -232,6 +270,7 @@
|
|||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Suprimeix",
|
||||
"AVATAR_DELETE_BUTTON_TEXT": "Delete Avatar",
|
||||
"CONFIRM": {
|
||||
"TITLE": "Confirma esborrat",
|
||||
"MESSAGE": "N'estas segur? ",
|
||||
|
@ -241,7 +280,9 @@
|
|||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "S'ha suprimit la safata d'entrada correctament",
|
||||
"ERROR_MESSAGE": "No s'ha pogut eliminar la safata d'entrada. Torneu-ho a provar més endavant."
|
||||
"ERROR_MESSAGE": "No s'ha pogut eliminar la safata d'entrada. Torneu-ho a provar més endavant.",
|
||||
"AVATAR_SUCCESS_MESSAGE": "Inbox avatar deleted successfully",
|
||||
"AVATAR_ERROR_MESSAGE": "Could not delete the inbox avatar. Please try again later."
|
||||
}
|
||||
},
|
||||
"TABS": {
|
||||
|
@ -273,7 +314,11 @@
|
|||
"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",
|
||||
"HMAC_VERIFICATION": "Validació de la Identitat del Usuari",
|
||||
"HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here."
|
||||
"HMAC_DESCRIPTION": "Inorder to validate the user's identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here.",
|
||||
"INBOX_IDENTIFIER": "Inbox Identifier",
|
||||
"INBOX_IDENTIFIER_SUB_TEXT": "Use the `inbox_identifier` token shown here to authentication your API clients.",
|
||||
"FORWARD_EMAIL_TITLE": "Forward to Email",
|
||||
"FORWARD_EMAIL_SUB_TEXT": "Comença a reenviar els teus correus electrònics a la següent adreça electrònica."
|
||||
},
|
||||
"FACEBOOK_REAUTHORIZE": {
|
||||
"TITLE": "Reautoritza",
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import { default as _agentMgmt } from './agentMgmt.json';
|
||||
import { default as _attributesMgmt } from './attributesMgmt.json';
|
||||
import { default as _campaign } from './campaign.json';
|
||||
import { default as _cannedMgmt } from './cannedMgmt.json';
|
||||
import { default as _chatlist } from './chatlist.json';
|
||||
import { default as _contact } from './contact.json';
|
||||
import { default as _conversation } from './conversation.json';
|
||||
import { default as _csatMgmtMgmt } from './csatMgmt.json';
|
||||
import { default as _generalSettings } from './generalSettings.json';
|
||||
import { default as _inboxMgmt } from './inboxMgmt.json';
|
||||
import { default as _integrationApps } from './integrationApps.json';
|
||||
import { default as _integrations } from './integrations.json';
|
||||
import { default as _labelsMgmt } from './labelsMgmt.json';
|
||||
import { default as _login } from './login.json';
|
||||
|
@ -18,13 +21,16 @@ import { default as _teamsSettings } from './teamsSettings.json';
|
|||
|
||||
export default {
|
||||
..._agentMgmt,
|
||||
..._attributesMgmt,
|
||||
..._campaign,
|
||||
..._cannedMgmt,
|
||||
..._chatlist,
|
||||
..._contact,
|
||||
..._conversation,
|
||||
..._csatMgmtMgmt,
|
||||
..._generalSettings,
|
||||
..._inboxMgmt,
|
||||
..._integrationApps,
|
||||
..._integrations,
|
||||
..._labelsMgmt,
|
||||
..._login,
|
||||
|
|
|
@ -102,6 +102,7 @@
|
|||
"CHANGE_ACCOUNTS": "Canvia de compte",
|
||||
"SELECTOR_SUBTITLE": "Selecciona un compte de la llista següent",
|
||||
"PROFILE_SETTINGS": "Configuració del Perfil",
|
||||
"KEYBOARD_SHORTCUTS": "Keyboard Shortcuts",
|
||||
"LOGOUT": "Sortir"
|
||||
},
|
||||
"APP_GLOBAL": {
|
||||
|
@ -130,8 +131,8 @@
|
|||
"SIDEBAR": {
|
||||
"CONVERSATIONS": "Converses",
|
||||
"REPORTS": "Informes",
|
||||
"CONTACTS": "Contactes",
|
||||
"SETTINGS": "Configuracions",
|
||||
"CONTACTS": "Contactes",
|
||||
"HOME": "Inici",
|
||||
"AGENTS": "Agents",
|
||||
"INBOXES": "Safates d'entrada",
|
||||
|
@ -141,13 +142,18 @@
|
|||
"ACCOUNT_SETTINGS": "Configuració del compte",
|
||||
"APPLICATIONS": "Applications",
|
||||
"LABELS": "Etiquetes",
|
||||
"ATTRIBUTES": "Attributes",
|
||||
"TEAMS": "Equips",
|
||||
"ALL_CONTACTS": "All Contacts",
|
||||
"TAGGED_WITH": "Tagged with",
|
||||
"REPORTS_OVERVIEW": "Overview",
|
||||
"CSAT": "CSAT"
|
||||
"CSAT": "CSAT",
|
||||
"CAMPAIGNS": "Campaigns",
|
||||
"ONGOING": "Ongoing",
|
||||
"ONE_OFF": "One off"
|
||||
},
|
||||
"CREATE_ACCOUNT": {
|
||||
"NO_ACCOUNT_WARNING": "Uh oh! We could not find any Chatwoot accounts. Please create a new account to continue.",
|
||||
"NEW_ACCOUNT": "Compte nou",
|
||||
"SELECTOR_SUBTITLE": "Crear un compte nou",
|
||||
"API": {
|
||||
|
@ -162,5 +168,30 @@
|
|||
},
|
||||
"SUBMIT": "Envia"
|
||||
}
|
||||
},
|
||||
"KEYBOARD_SHORTCUTS": {
|
||||
"TITLE": {
|
||||
"OPEN_CONVERSATION": "Open conversation",
|
||||
"RESOLVE_AND_NEXT": "Resolve and move to next",
|
||||
"NAVIGATE_DROPDOWN": "Navigate dropdown items",
|
||||
"RESOLVE_CONVERSATION": "Resolve Conversation",
|
||||
"GO_TO_CONVERSATION_DASHBOARD": "Go to Conversation Dashboard",
|
||||
"ADD_ATTACHMENT": "Add Attachment",
|
||||
"GO_TO_CONTACTS_DASHBOARD": "Go to Contacts Dashboard",
|
||||
"TOGGLE_SIDEBAR": "Toggle Sidebar",
|
||||
"GO_TO_REPORTS_SIDEBAR": "Go to Reports sidebar",
|
||||
"MOVE_TO_NEXT_TAB": "Move to next tab in conversation list",
|
||||
"GO_TO_SETTINGS": "Go to Settings",
|
||||
"SWITCH_CONVERSATION_STATUS": "Switch to the next conversation status",
|
||||
"SWITCH_TO_PRIVATE_NOTE": "Switch to Private Note",
|
||||
"TOGGLE_RICH_CONTENT_EDITOR": "Toggle Rich Content editor",
|
||||
"SWITCH_TO_REPLY": "Switch to Reply",
|
||||
"TOGGLE_SNOOZE_DROPDOWN": "Toggle snooze dropdown"
|
||||
},
|
||||
"KEYS": {
|
||||
"WINDOWS_KEY_AND_COMMAND_KEY": "Win / ⌘",
|
||||
"ALT_OR_OPTION_KEY": "Alt / ⌥",
|
||||
"FORWARD_SLASH_KEY": "/"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
"HEADER": "Agenti",
|
||||
"HEADER_BTN_TXT": "Přidat agenta",
|
||||
"LOADING": "Načítání seznamu agentů",
|
||||
"SIDEBAR_TXT": "<p><b>Agents</b></p> <p> An <b>Agent</b> is a member of your Customer Support team. </p><p> Agents will be able to view and reply to messages from your users. The list shows all agents currently in your account. </p><p> Click on <b>Add Agent</b> to add a new agent. Agent you add will receive an email with a confirmation link to activate their account, after which they can access Chatwoot and respond to messages. </p><p> Access to Chatwoot's features are based on following roles. </p><p> <b>Agent</b> - Agents with this role can only access inboxes, reports and conversations. They can assign conversations to other agents or themselves and resolve conversations.</p><p> <b>Administrator</b> - Administrator will have access to all Chatwoot features enabled for your account, including settings, along with all of a normal agents' privileges.</p>",
|
||||
"SIDEBAR_TXT": "<p><b>Agenti</b></p> <p> <b>Agent</b> je členem vašeho týmu zákaznické podpory. </p><p> Agenti budou moci prohlížet a odpovídat na zprávy od uživatelů. Seznam zobrazuje všechny agenty aktuálně na vašem účtu. </p><p> Pro přidání nového agenta klikněte na <b>Přidat agenta</b>. Přidaný agent obdrží e-mail s potvrzovacím odkazem pro aktivaci jejich účtu, poté bude mít přístup k Chatwoot a bude reagovat na zprávy. </p><p> Přístup k funkcím Chatwootu je založen na následujících rolích. </p><p> <b>Agent</b> - Agent s touto rolí může přistupovat pouze k doručeným zprávám, zprávám a konverzacím. Mohou přiřadit konverzace jiným agentům nebo sobě a řešit konverzace.</p><p> <b>Administrátor</b> - Správce bude mít přístup ke všem funkcím Chatwoot povoleným pro váš účet, včetně nastavení spolu se všemi obvyklými právy agenta.</p>",
|
||||
"AGENT_TYPES": {
|
||||
"ADMINISTRATOR": "Administrátor",
|
||||
"AGENT": "Agent"
|
||||
|
@ -91,6 +91,23 @@
|
|||
},
|
||||
"SEARCH": {
|
||||
"NO_RESULTS": "Žádné výsledky."
|
||||
},
|
||||
"MULTI_SELECTOR": {
|
||||
"PLACEHOLDER": "Nic",
|
||||
"TITLE": {
|
||||
"AGENT": "Vybrat agenta",
|
||||
"TEAM": "Vybrat tým"
|
||||
},
|
||||
"SEARCH": {
|
||||
"NO_RESULTS": {
|
||||
"AGENT": "Nenalezeni žádní agenti",
|
||||
"TEAM": "Nenalezeny žádné týmy"
|
||||
},
|
||||
"PLACEHOLDER": {
|
||||
"AGENT": "Hledat agenty",
|
||||
"TEAM": "Hledat týmy"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
85
app/javascript/dashboard/i18n/locale/cs/attributesMgmt.json
Normal file
85
app/javascript/dashboard/i18n/locale/cs/attributesMgmt.json
Normal file
|
@ -0,0 +1,85 @@
|
|||
{
|
||||
"ATTRIBUTES_MGMT": {
|
||||
"HEADER": "Attributes",
|
||||
"HEADER_BTN_TXT": "Add Attribute",
|
||||
"LOADING": "Fetching attributes",
|
||||
"SIDEBAR_TXT": "<p><b>Attributes</b> <p>A custom attribute tracks facts about your contacts/conversation — like the subscription plan, or when they ordered the first item etc. <br /><br />For creating a Attributes, just click on the <b>Add Attribute.</b> You can also edit or delete an existing Attribute by clicking on the Edit or Delete button.</p>",
|
||||
"ADD": {
|
||||
"TITLE": "Add attribute",
|
||||
"SUBMIT": "Create",
|
||||
"CANCEL_BUTTON_TEXT": "Zrušit",
|
||||
"FORM": {
|
||||
"NAME": {
|
||||
"LABEL": "Display Name",
|
||||
"PLACEHOLDER": "Enter attribute display name",
|
||||
"ERROR": "Name is required"
|
||||
},
|
||||
"DESC": {
|
||||
"LABEL": "Description",
|
||||
"PLACEHOLDER": "Enter attribute description",
|
||||
"ERROR": "Description is required"
|
||||
},
|
||||
"MODEL": {
|
||||
"LABEL": "Model",
|
||||
"PLACEHOLDER": "Please select a model",
|
||||
"ERROR": "Model is required"
|
||||
},
|
||||
"TYPE": {
|
||||
"LABEL": "Type",
|
||||
"PLACEHOLDER": "Please select a type",
|
||||
"ERROR": "Type is required"
|
||||
},
|
||||
"KEY": {
|
||||
"LABEL": "Key"
|
||||
}
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Attribute added successfully",
|
||||
"ERROR_MESSAGE": "Could not able to create an attribute, Please try again later"
|
||||
}
|
||||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Vymazat",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Attribute deleted successfully.",
|
||||
"ERROR_MESSAGE": "Couldn't delete the attribute. Try again."
|
||||
},
|
||||
"CONFIRM": {
|
||||
"TITLE": "Are you sure want to delete - %{attributeName}",
|
||||
"PLACE_HOLDER": "Please type {attributeName} to confirm",
|
||||
"MESSAGE": "Deleting will remove the attribute",
|
||||
"YES": "Vymazat ",
|
||||
"NO": "Zrušit"
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"TITLE": "Edit attribute",
|
||||
"UPDATE_BUTTON_TEXT": "Aktualizovat",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Attribute updated successfully",
|
||||
"ERROR_MESSAGE": "There was an error updating attribute, please try again"
|
||||
}
|
||||
},
|
||||
"TABS": {
|
||||
"HEADER": "Vlastní atributy",
|
||||
"CONVERSATION": "Conversation",
|
||||
"CONTACT": "Contact"
|
||||
},
|
||||
"LIST": {
|
||||
"TABLE_HEADER": [
|
||||
"Název",
|
||||
"Description",
|
||||
"Type",
|
||||
"Key"
|
||||
],
|
||||
"BUTTONS": {
|
||||
"EDIT": "Upravit",
|
||||
"DELETE": "Vymazat"
|
||||
},
|
||||
"EMPTY_RESULT": {
|
||||
"404": "There are no attributes created",
|
||||
"NOT_FOUND": "There are no attributes configured"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,8 +1,11 @@
|
|||
{
|
||||
"CAMPAIGN": {
|
||||
"HEADER": "Campaigns",
|
||||
"HEADER": "Kampaně",
|
||||
"SIDEBAR_TXT": "Proactive messages allow the customer to send outbound messages to their contacts which would trigger more conversations. Click on <b>Add Campaign</b> to create a new campaign. You can also edit or delete an existing campaign by clicking on the Edit or Delete button.",
|
||||
"HEADER_BTN_TXT": "Create a campaign",
|
||||
"HEADER_BTN_TXT": {
|
||||
"ONE_OFF": "Create a one off campaign",
|
||||
"ONGOING": "Create a ongoing campaign"
|
||||
},
|
||||
"ADD": {
|
||||
"TITLE": "Create a campaign",
|
||||
"DESC": "Proactive messages allow the customer to send outbound messages to their contacts which would trigger more conversations.",
|
||||
|
@ -25,6 +28,11 @@
|
|||
"PLACEHOLDER": "Select the customer labels",
|
||||
"ERROR": "Audience is required"
|
||||
},
|
||||
"INBOX": {
|
||||
"LABEL": "Select Inbox",
|
||||
"PLACEHOLDER": "Select Inbox",
|
||||
"ERROR": "Inbox is required"
|
||||
},
|
||||
"MESSAGE": {
|
||||
"LABEL": "Zpráva",
|
||||
"PLACEHOLDER": "Please enter the message of campaign",
|
||||
|
@ -80,6 +88,7 @@
|
|||
"TABLE_HEADER": {
|
||||
"TITLE": "Title",
|
||||
"MESSAGE": "Zpráva",
|
||||
"INBOX": "Inbox",
|
||||
"STATUS": "Stav",
|
||||
"SENDER": "Sender",
|
||||
"URL": "URL",
|
||||
|
@ -101,6 +110,16 @@
|
|||
"SENDER": {
|
||||
"BOT": "Bot"
|
||||
}
|
||||
},
|
||||
"ONE_OFF": {
|
||||
"HEADER": "One off campaigns",
|
||||
"404": "There are no one off campaigns created",
|
||||
"INBOXES_NOT_FOUND": "Please create an sms inbox and start adding campaigns"
|
||||
},
|
||||
"ONGOING": {
|
||||
"HEADER": "Ongoing campaigns",
|
||||
"404": "There are no ongoing campaigns created",
|
||||
"INBOXES_NOT_FOUND": "Please create an website inbox and start adding campaigns"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,8 +47,12 @@
|
|||
"VALUE": "resolved"
|
||||
},
|
||||
{
|
||||
"TEXT": "Bot",
|
||||
"VALUE": "bot"
|
||||
"TEXT": "Čekající",
|
||||
"VALUE": "pending"
|
||||
},
|
||||
{
|
||||
"TEXT": "Odložené",
|
||||
"VALUE": "snoozed"
|
||||
}
|
||||
],
|
||||
"ATTACHMENTS": {
|
||||
|
@ -81,6 +85,6 @@
|
|||
"VIEW_TWEET_IN_TWITTER": "Zobrazit tweet na Twitteru",
|
||||
"REPLY_TO_TWEET": "Odpovědět na tento tweet",
|
||||
"NO_MESSAGES": "Žádné zprávy",
|
||||
"NO_CONTENT": "No content available"
|
||||
"NO_CONTENT": "Žádný obsah k dispozici"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,7 +37,12 @@
|
|||
"MUTED_SUCCESS": "Tato konverzace je ztlumena na 6 hodin",
|
||||
"UNMUTED_SUCCESS": "Tato konverzace je odtlumena",
|
||||
"SEND_TRANSCRIPT": "Poslat přepis",
|
||||
"EDIT_LABEL": "Upravit"
|
||||
"EDIT_LABEL": "Upravit",
|
||||
"SIDEBAR_SECTIONS": {
|
||||
"CUSTOM_ATTRIBUTES": "Vlastní atributy",
|
||||
"CONTACT_LABELS": "Contact Labels",
|
||||
"PREVIOUS_CONVERSATIONS": "Předchozí konverzace"
|
||||
}
|
||||
},
|
||||
"EDIT_CONTACT": {
|
||||
"BUTTON_LABEL": "Upravit kontakt",
|
||||
|
@ -107,7 +112,7 @@
|
|||
},
|
||||
"NEW_CONVERSATION": {
|
||||
"BUTTON_LABEL": "Start conversation",
|
||||
"TITLE": "New conversation",
|
||||
"TITLE": "Nová konverzace",
|
||||
"DESC": "Start a new conversation by sending a new message.",
|
||||
"NO_INBOX": "Couldn't find an inbox to initiate a new conversation with this contact.",
|
||||
"FORM": {
|
||||
|
@ -186,8 +191,8 @@
|
|||
}
|
||||
},
|
||||
"CUSTOM_ATTRIBUTES": {
|
||||
"TITLE": "Vlastní atributy",
|
||||
"BUTTON": "Add custom attribute",
|
||||
"NOT_AVAILABLE": "There are no custom attributes available for this contact.",
|
||||
"ADD": {
|
||||
"TITLE": "Create custom attribute",
|
||||
"DESC": "Add custom information to this contact."
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
"SEARCH_MESSAGES": "Hledat zprávy v konverzacích",
|
||||
"SEARCH": {
|
||||
"TITLE": "Hledat zprávy",
|
||||
"RESULT_TITLE": "Výsledky hledání",
|
||||
"LOADING_MESSAGE": "Načítám data...",
|
||||
"PLACEHOLDER": "Zadejte jakýkoli text k hledání",
|
||||
"NO_MATCHING_RESULTS": "Žádné výsledky."
|
||||
|
@ -22,16 +23,16 @@
|
|||
"24_HOURS_WINDOW": "24 hodinové omezení okna",
|
||||
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
|
||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 hodinové omezení okna",
|
||||
"LAST_INCOMING_TWEET": "Odpovídáte na poslední příchozí tweet",
|
||||
"SELECT_A_TWEET_TO_REPLY": "Please select a tweet to reply to.",
|
||||
"REPLYING_TO": "Odpovídáte uživateli:",
|
||||
"REMOVE_SELECTION": "Odstranit výběr",
|
||||
"DOWNLOAD": "Stáhnout",
|
||||
"UPLOADING_ATTACHMENTS": "Nahrávání příloh...",
|
||||
"SUCCESS_DELETE_MESSAGE": "Message deleted successfully",
|
||||
"SUCCESS_DELETE_MESSAGE": "Zpráva byla úspěšně smazána",
|
||||
"FAIL_DELETE_MESSSAGE": "Couldn't delete message! Try again",
|
||||
"NO_RESPONSE": "Bez odpovědi",
|
||||
"RATING_TITLE": "Rating",
|
||||
"FEEDBACK_TITLE": "Feedback",
|
||||
"RATING_TITLE": "Hodnocení",
|
||||
"FEEDBACK_TITLE": "Zpětná vazba",
|
||||
"HEADER": {
|
||||
"RESOLVE_ACTION": "Vyřešit",
|
||||
"REOPEN_ACTION": "Znovu otevřít",
|
||||
|
@ -41,7 +42,13 @@
|
|||
"DETAILS": "Podrobnosti"
|
||||
},
|
||||
"RESOLVE_DROPDOWN": {
|
||||
"OPEN_BOT": "Open with bot"
|
||||
"MARK_PENDING": "Mark as pending",
|
||||
"SNOOZE": {
|
||||
"TITLE": "Odložit do",
|
||||
"NEXT_REPLY": "Další odpověď",
|
||||
"TOMORROW": "Zítra",
|
||||
"NEXT_WEEK": "Příští týden"
|
||||
}
|
||||
},
|
||||
"FOOTER": {
|
||||
"MSG_INPUT": "Shift + zadejte pro nový řádek. Začněte '/' pro výběr zrušené odpovědi.",
|
||||
|
@ -57,13 +64,26 @@
|
|||
"TIP_EMOJI_ICON": "Zobrazit výběr emoji",
|
||||
"TIP_ATTACH_ICON": "Přiložit soubory",
|
||||
"ENTER_TO_SEND": "Enter to send",
|
||||
"DRAG_DROP": "Drag and drop here to attach"
|
||||
"DRAG_DROP": "Drag and drop here to attach",
|
||||
"EMAIL_HEAD": {
|
||||
"ADD_BCC": "Přidat bcc",
|
||||
"CC": {
|
||||
"LABEL": "CC",
|
||||
"PLACEHOLDER": "E-maily oddělené čárkami",
|
||||
"ERROR": "Zadejte prosím platnou e-mailovou adresu"
|
||||
},
|
||||
"BCC": {
|
||||
"LABEL": "BCC",
|
||||
"PLACEHOLDER": "E-maily oddělené čárkami",
|
||||
"ERROR": "Zadejte prosím platnou e-mailovou adresu"
|
||||
}
|
||||
}
|
||||
},
|
||||
"VISIBLE_TO_AGENTS": "Soukromá poznámka: Viditelné pouze pro vás a váš tým",
|
||||
"CHANGE_STATUS": "Stav konverzace byl změněn",
|
||||
"CHANGE_AGENT": "Konverzace pověřená osoba změněna",
|
||||
"CHANGE_TEAM": "Tým konverzace se změnil",
|
||||
"FILE_SIZE_LIMIT": "File exceeds the {MAXIMUM_FILE_UPLOAD_SIZE} attachment limit",
|
||||
"FILE_SIZE_LIMIT": "Soubor překračuje limit {MAXIMUM_FILE_UPLOAD_SIZE} přílohy",
|
||||
"SENT_BY": "Odeslal:",
|
||||
"ASSIGNMENT": {
|
||||
"SELECT_AGENT": "Vybrat agenta",
|
||||
|
@ -98,11 +118,11 @@
|
|||
"READ_LATEST_UPDATES": "Přečtěte si nejnovější aktualizace",
|
||||
"ALL_CONVERSATION": {
|
||||
"TITLE": "Všechny vaše konverzace na jednom místě",
|
||||
"DESCRIPTION": "View all the conversations from your customers in one single dashboard. You can filter the conversations by the incoming channel, label and status."
|
||||
"DESCRIPTION": "Zobrazit všechny konverzace od zákazníků na jednom nástěnce. Můžete filtrovat konverzace podle příchozího kanálu, popisku a stavu."
|
||||
},
|
||||
"TEAM_MEMBERS": {
|
||||
"TITLE": "Pozvěte své členy týmu",
|
||||
"DESCRIPTION": "Since you are getting ready to talk to your customer, bring in your teammates to assist you. You can invite your teammates by adding their email address to the agent list.",
|
||||
"DESCRIPTION": "Vzhledem k tomu, že se připravujete na rozhovory se zákazníkem, vdechněte své týmové spolupracovníky, kteří vám pomohou. Můžete pozvat své přátele přidáním jejich e-mailové adresy do seznamu agentů.",
|
||||
"NEW_LINK": "Klikněte zde pro pozvání člena týmu"
|
||||
},
|
||||
"INBOXES": {
|
||||
|
@ -118,10 +138,24 @@
|
|||
},
|
||||
"CONVERSATION_SIDEBAR": {
|
||||
"ASSIGNEE_LABEL": "Přiřazený agent",
|
||||
"SELF_ASSIGN": "Assign to me",
|
||||
"SELF_ASSIGN": "Přiřadit mi",
|
||||
"TEAM_LABEL": "Přiřazený tým",
|
||||
"SELECT": {
|
||||
"PLACEHOLDER": "Nic"
|
||||
},
|
||||
"ACCORDION": {
|
||||
"CONTACT_DETAILS": "Contact Details",
|
||||
"CONVERSATION_ACTIONS": "Conversation Actions",
|
||||
"CONVERSATION_LABELS": "Conversation Labels",
|
||||
"CONVERSATION_INFO": "Conversation Information",
|
||||
"CONTACT_ATTRIBUTES": "Contact Attributes",
|
||||
"PREVIOUS_CONVERSATION": "Předchozí konverzace"
|
||||
}
|
||||
},
|
||||
"EMAIL_HEADER": {
|
||||
"TO": "Komu",
|
||||
"BCC": "Bcc",
|
||||
"CC": "Cc",
|
||||
"SUBJECT": "Předmět"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"CSAT": {
|
||||
"TITLE": "Rate your conversation",
|
||||
"PLACEHOLDER": "Tell us more..."
|
||||
"TITLE": "Ohodnoťte svou konverzaci",
|
||||
"PLACEHOLDER": "Řekněte nám více..."
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@
|
|||
"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."
|
||||
"UPDATE_CHATWOOT": "Je dostupná aktualizace %{latestChatwootVersion} pro Chatwoot. Aktualizujte prosím svou instanci."
|
||||
},
|
||||
"FORMS": {
|
||||
"MULTISELECT": {
|
||||
|
@ -53,11 +53,11 @@
|
|||
}
|
||||
},
|
||||
"NOTIFICATIONS_PAGE": {
|
||||
"HEADER": "Notifications",
|
||||
"MARK_ALL_DONE": "Mark All Done",
|
||||
"HEADER": "Oznámení",
|
||||
"MARK_ALL_DONE": "Označit vše dokončeno",
|
||||
"LIST": {
|
||||
"LOADING_MESSAGE": "Loading notifications...",
|
||||
"404": "No Notifications",
|
||||
"LOADING_MESSAGE": "Načítání upozornění...",
|
||||
"404": "Žádná upozornění",
|
||||
"TABLE_HEADER": [
|
||||
"Název",
|
||||
"Telefonní číslo",
|
||||
|
@ -66,10 +66,10 @@
|
|||
]
|
||||
},
|
||||
"TYPE_LABEL": {
|
||||
"conversation_creation": "New conversation",
|
||||
"conversation_assignment": "Conversation Assigned",
|
||||
"assigned_conversation_new_message": "New Message",
|
||||
"conversation_mention": "Mention"
|
||||
"conversation_creation": "Nová konverzace",
|
||||
"conversation_assignment": "Přiřazená konverzace",
|
||||
"assigned_conversation_new_message": "Nová zpráva",
|
||||
"conversation_mention": "Zmínka"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -172,6 +172,44 @@
|
|||
},
|
||||
"FINISH_MESSAGE": "Start forwarding your emails to the following email address."
|
||||
},
|
||||
"LINE_CHANNEL": {
|
||||
"TITLE": "LINE Channel",
|
||||
"DESC": "Integrate with LINE channel and start supporting your customers.",
|
||||
"CHANNEL_NAME": {
|
||||
"LABEL": "Název kanálu",
|
||||
"PLACEHOLDER": "Zadejte název kanálu",
|
||||
"ERROR": "Toto pole je povinné"
|
||||
},
|
||||
"LINE_CHANNEL_ID": {
|
||||
"LABEL": "LINE Channel ID",
|
||||
"PLACEHOLDER": "LINE Channel ID"
|
||||
},
|
||||
"LINE_CHANNEL_SECRET": {
|
||||
"LABEL": "LINE Channel Secret",
|
||||
"PLACEHOLDER": "LINE Channel Secret"
|
||||
},
|
||||
"LINE_CHANNEL_TOKEN": {
|
||||
"LABEL": "LINE Channel Token",
|
||||
"PLACEHOLDER": "LINE Channel Token"
|
||||
},
|
||||
"SUBMIT_BUTTON": "Create LINE Channel",
|
||||
"API": {
|
||||
"ERROR_MESSAGE": "We were not able to save the LINE channel"
|
||||
}
|
||||
},
|
||||
"TELEGRAM_CHANNEL": {
|
||||
"TITLE": "Telegram Channel",
|
||||
"DESC": "Integrate with Telegram channel and start supporting your customers.",
|
||||
"BOT_TOKEN": {
|
||||
"LABEL": "Bot Token",
|
||||
"SUBTITLE": "Configure the bot token you have obtained from Telegram BotFather.",
|
||||
"PLACEHOLDER": "Bot Token"
|
||||
},
|
||||
"SUBMIT_BUTTON": "Create Telegram Channel",
|
||||
"API": {
|
||||
"ERROR_MESSAGE": "We were not able to save the telegram channel"
|
||||
}
|
||||
},
|
||||
"AUTH": {
|
||||
"TITLE": "Choose a channel",
|
||||
"DESC": "Chatwoot supports live-chat widget, Facebook page, Twitter profile, Whatsapp, Email etc., as channels. If you want to build a custom channel, you can create it using the API channel. Select one channel from the options below to proceed."
|
||||
|
@ -232,6 +270,7 @@
|
|||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Vymazat",
|
||||
"AVATAR_DELETE_BUTTON_TEXT": "Delete Avatar",
|
||||
"CONFIRM": {
|
||||
"TITLE": "Potvrdit odstranění",
|
||||
"MESSAGE": "Opravdu chcete odstranit ",
|
||||
|
@ -241,14 +280,16 @@
|
|||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Doručená pošta byla úspěšně smazána",
|
||||
"ERROR_MESSAGE": "Nelze odstranit doručenou poštu. Opakujte akci později."
|
||||
"ERROR_MESSAGE": "Nelze odstranit doručenou poštu. Opakujte akci později.",
|
||||
"AVATAR_SUCCESS_MESSAGE": "Inbox avatar deleted successfully",
|
||||
"AVATAR_ERROR_MESSAGE": "Could not delete the inbox avatar. Please try again later."
|
||||
}
|
||||
},
|
||||
"TABS": {
|
||||
"SETTINGS": "Nastavení",
|
||||
"COLLABORATORS": "Spolupracující",
|
||||
"CONFIGURATION": "Nastavení",
|
||||
"CAMPAIGN": "Campaigns",
|
||||
"CAMPAIGN": "Kampaně",
|
||||
"PRE_CHAT_FORM": "Formulář před chatem",
|
||||
"BUSINESS_HOURS": "Pracovní doba"
|
||||
},
|
||||
|
@ -273,13 +314,17 @@
|
|||
"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.",
|
||||
"HMAC_VERIFICATION": "User Identity Validation",
|
||||
"HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here."
|
||||
"HMAC_DESCRIPTION": "Inorder to validate the user's identity, the SDK allows you to pass an `identifier_hash` for each user. You can generate HMAC using 'sha256' with the key shown here.",
|
||||
"INBOX_IDENTIFIER": "Inbox Identifier",
|
||||
"INBOX_IDENTIFIER_SUB_TEXT": "Use the `inbox_identifier` token shown here to authentication your API clients.",
|
||||
"FORWARD_EMAIL_TITLE": "Forward to Email",
|
||||
"FORWARD_EMAIL_SUB_TEXT": "Start forwarding your emails to the following email address."
|
||||
},
|
||||
"FACEBOOK_REAUTHORIZE": {
|
||||
"TITLE": "Znovu autorizovat",
|
||||
"SUBTITLE": "Your Facebook connection has expired, please reconnect your Facebook page to continue services",
|
||||
"MESSAGE_SUCCESS": "Reconnection successful",
|
||||
"MESSAGE_ERROR": "There was an error, please try again"
|
||||
"MESSAGE_ERROR": "Došlo k chybě, zkuste to prosím znovu"
|
||||
},
|
||||
"PRE_CHAT_FORM": {
|
||||
"DESCRIPTION": "Pre chat forms enable you to capture user information before they start conversation with you.",
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import { default as _agentMgmt } from './agentMgmt.json';
|
||||
import { default as _attributesMgmt } from './attributesMgmt.json';
|
||||
import { default as _campaign } from './campaign.json';
|
||||
import { default as _cannedMgmt } from './cannedMgmt.json';
|
||||
import { default as _chatlist } from './chatlist.json';
|
||||
import { default as _contact } from './contact.json';
|
||||
import { default as _conversation } from './conversation.json';
|
||||
import { default as _csatMgmtMgmt } from './csatMgmt.json';
|
||||
import { default as _generalSettings } from './generalSettings.json';
|
||||
import { default as _inboxMgmt } from './inboxMgmt.json';
|
||||
import { default as _integrationApps } from './integrationApps.json';
|
||||
import { default as _integrations } from './integrations.json';
|
||||
import { default as _labelsMgmt } from './labelsMgmt.json';
|
||||
import { default as _login } from './login.json';
|
||||
|
@ -18,13 +21,16 @@ import { default as _teamsSettings } from './teamsSettings.json';
|
|||
|
||||
export default {
|
||||
..._agentMgmt,
|
||||
..._attributesMgmt,
|
||||
..._campaign,
|
||||
..._cannedMgmt,
|
||||
..._chatlist,
|
||||
..._contact,
|
||||
..._conversation,
|
||||
..._csatMgmtMgmt,
|
||||
..._generalSettings,
|
||||
..._inboxMgmt,
|
||||
..._integrationApps,
|
||||
..._integrations,
|
||||
..._labelsMgmt,
|
||||
..._login,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue