Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3496c9c16a | ||
|
|
1b17926b90 | ||
|
|
b21e9849ba | ||
|
|
64db949581 | ||
|
|
8c1c1a5ab2 | ||
|
|
2971d78f8c | ||
|
|
7f8d8a112d | ||
|
|
68205d8fd3 | ||
|
|
d42206e03b |
@@ -15,9 +15,9 @@ defaults: &defaults
|
||||
- image: cimg/postgres:15.3
|
||||
- image: cimg/redis:6.2.6
|
||||
environment:
|
||||
- RAILS_LOG_TO_STDOUT: false
|
||||
- COVERAGE: true
|
||||
- LOG_LEVEL: warn
|
||||
- RAILS_LOG_TO_STDOUT: false
|
||||
- COVERAGE: true
|
||||
- LOG_LEVEL: warn
|
||||
parallelism: 4
|
||||
resource_class: large
|
||||
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
export NVM_DIR="$HOME/.nvm"
|
||||
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
|
||||
[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
|
||||
nvm install v20
|
||||
nvm install v16
|
||||
echo 'export NVM_DIR="$HOME/.nvm"' >> $BASH_ENV
|
||||
echo '[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV
|
||||
|
||||
@@ -64,6 +64,7 @@ jobs:
|
||||
- ~/.bundle
|
||||
key: chatwoot-bundle-{{ .Environment.CACHE_VERSION }}-v20220524-{{ checksum "Gemfile.lock" }}
|
||||
|
||||
|
||||
# Only necessary if app uses webpacker or yarn in some other way
|
||||
- restore_cache:
|
||||
keys:
|
||||
@@ -81,7 +82,7 @@ jobs:
|
||||
- ~/.cache/yarn
|
||||
|
||||
- run:
|
||||
name: Download cc-test-reporter
|
||||
name: Download cc-test-reporter
|
||||
command: |
|
||||
mkdir -p ~/tmp
|
||||
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ~/tmp/cc-test-reporter
|
||||
@@ -181,7 +182,7 @@ jobs:
|
||||
- attach_workspace:
|
||||
at: ~/build
|
||||
- run:
|
||||
name: Download cc-test-reporter
|
||||
name: Download cc-test-reporter
|
||||
command: |
|
||||
mkdir -p ~/tmp
|
||||
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ~/tmp/cc-test-reporter
|
||||
@@ -203,4 +204,4 @@ workflows:
|
||||
- build
|
||||
- upload-coverage:
|
||||
requires:
|
||||
- build
|
||||
- build
|
||||
|
||||
@@ -199,8 +199,6 @@ ANDROID_SHA256_CERT_FINGERPRINT=AC:73:8E:DE:EB:56:EA:CC:10:87:02:A7:65:37:7B:38:
|
||||
## Rack Attack configuration
|
||||
## To prevent and throttle abusive requests
|
||||
# ENABLE_RACK_ATTACK=true
|
||||
# RACK_ATTACK_IP_LIMIT=3000
|
||||
# ENABLE_RACK_ATTACK_WIDGET_API=true
|
||||
|
||||
## Running chatwoot as an API only server
|
||||
## setting this value to true will disable the frontend dashboard endpoints
|
||||
|
||||
@@ -52,8 +52,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
cache: yarn
|
||||
node-version: 16
|
||||
|
||||
- name: yarn
|
||||
run: yarn install
|
||||
@@ -76,8 +75,6 @@ jobs:
|
||||
- name: Run backend tests
|
||||
run: |
|
||||
bundle exec rspec --profile=10 --format documentation
|
||||
env:
|
||||
NODE_OPTIONS: --openssl-legacy-provider
|
||||
|
||||
- name: Upload rails log folder
|
||||
uses: actions/upload-artifact@v3
|
||||
|
||||
@@ -51,8 +51,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
cache: yarn
|
||||
node-version: 16
|
||||
|
||||
- name: yarn
|
||||
run: yarn install
|
||||
|
||||
@@ -21,8 +21,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 20
|
||||
cache: 'yarn'
|
||||
node-version: 16
|
||||
|
||||
- name: yarn
|
||||
run: yarn install
|
||||
@@ -31,11 +30,9 @@ jobs:
|
||||
run: |
|
||||
rm -rf enterprise
|
||||
rm -rf spec/enterprise
|
||||
|
||||
|
||||
- name: Run asset compile
|
||||
run: bundle exec rake assets:precompile
|
||||
env:
|
||||
NODE_OPTIONS: --openssl-legacy-provider
|
||||
|
||||
- name: Size Check
|
||||
run: yarn run size
|
||||
|
||||
+7
-3
@@ -8,11 +8,15 @@ Layout/LineLength:
|
||||
Max: 150
|
||||
|
||||
Metrics/ClassLength:
|
||||
Max: 175
|
||||
Max: 125
|
||||
Exclude:
|
||||
- 'app/models/message.rb'
|
||||
- 'app/models/conversation.rb'
|
||||
|
||||
- 'app/models/contact.rb'
|
||||
- 'app/mailers/conversation_reply_mailer.rb'
|
||||
- 'app/models/message.rb'
|
||||
- 'app/builders/messages/facebook/message_builder.rb'
|
||||
- 'app/controllers/api/v1/accounts/contacts_controller.rb'
|
||||
- 'app/listeners/action_cable_listener.rb'
|
||||
RSpec/ExampleLength:
|
||||
Max: 25
|
||||
Style/Documentation:
|
||||
|
||||
@@ -4,7 +4,7 @@ ruby '3.2.2'
|
||||
|
||||
##-- base gems for rails --##
|
||||
gem 'rack-cors', require: 'rack/cors'
|
||||
gem 'rails', '~> 7.0.8.0'
|
||||
gem 'rails', '~> 7.0.7.2'
|
||||
# Reduces boot times through caching; required in config/boot.rb
|
||||
gem 'bootsnap', require: false
|
||||
|
||||
@@ -108,14 +108,14 @@ gem 'elastic-apm', require: false
|
||||
gem 'newrelic_rpm', require: false
|
||||
gem 'newrelic-sidekiq-metrics', require: false
|
||||
gem 'scout_apm', require: false
|
||||
gem 'sentry-rails', '>= 5.11.0', require: false
|
||||
gem 'sentry-rails', '>= 5.10.0', require: false
|
||||
gem 'sentry-ruby', require: false
|
||||
gem 'sentry-sidekiq', '>= 5.11.0', require: false
|
||||
gem 'sentry-sidekiq', '>= 5.10.0', require: false
|
||||
|
||||
##-- background job processing --##
|
||||
gem 'sidekiq', '>= 7.1.3'
|
||||
gem 'sidekiq'
|
||||
# We want cron jobs
|
||||
gem 'sidekiq-cron', '>= 1.10.1'
|
||||
gem 'sidekiq-cron'
|
||||
|
||||
##-- Push notification service --##
|
||||
gem 'fcm'
|
||||
@@ -188,7 +188,7 @@ group :development do
|
||||
gem 'bullet'
|
||||
gem 'letter_opener'
|
||||
gem 'scss_lint', require: false
|
||||
gem 'web-console', '>= 4.2.1'
|
||||
gem 'web-console'
|
||||
|
||||
# used in swagger build
|
||||
gem 'json_refs'
|
||||
|
||||
+80
-80
@@ -33,70 +33,70 @@ GIT
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
actioncable (7.0.7.2)
|
||||
actionpack (= 7.0.7.2)
|
||||
activesupport (= 7.0.7.2)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activejob (= 7.0.8)
|
||||
activerecord (= 7.0.8)
|
||||
activestorage (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
actionmailbox (7.0.7.2)
|
||||
actionpack (= 7.0.7.2)
|
||||
activejob (= 7.0.7.2)
|
||||
activerecord (= 7.0.7.2)
|
||||
activestorage (= 7.0.7.2)
|
||||
activesupport (= 7.0.7.2)
|
||||
mail (>= 2.7.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
actionmailer (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
actionview (= 7.0.8)
|
||||
activejob (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
actionmailer (7.0.7.2)
|
||||
actionpack (= 7.0.7.2)
|
||||
actionview (= 7.0.7.2)
|
||||
activejob (= 7.0.7.2)
|
||||
activesupport (= 7.0.7.2)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (7.0.8)
|
||||
actionview (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
actionpack (7.0.7.2)
|
||||
actionview (= 7.0.7.2)
|
||||
activesupport (= 7.0.7.2)
|
||||
rack (~> 2.0, >= 2.2.4)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activerecord (= 7.0.8)
|
||||
activestorage (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
actiontext (7.0.7.2)
|
||||
actionpack (= 7.0.7.2)
|
||||
activerecord (= 7.0.7.2)
|
||||
activestorage (= 7.0.7.2)
|
||||
activesupport (= 7.0.7.2)
|
||||
globalid (>= 0.6.0)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
actionview (7.0.7.2)
|
||||
activesupport (= 7.0.7.2)
|
||||
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 (7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
activejob (7.0.7.2)
|
||||
activesupport (= 7.0.7.2)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
activerecord (7.0.8)
|
||||
activemodel (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
activemodel (7.0.7.2)
|
||||
activesupport (= 7.0.7.2)
|
||||
activerecord (7.0.7.2)
|
||||
activemodel (= 7.0.7.2)
|
||||
activesupport (= 7.0.7.2)
|
||||
activerecord-import (1.4.1)
|
||||
activerecord (>= 4.2)
|
||||
activestorage (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activejob (= 7.0.8)
|
||||
activerecord (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
activestorage (7.0.7.2)
|
||||
actionpack (= 7.0.7.2)
|
||||
activejob (= 7.0.7.2)
|
||||
activerecord (= 7.0.7.2)
|
||||
activesupport (= 7.0.7.2)
|
||||
marcel (~> 1.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (7.0.8)
|
||||
activesupport (7.0.7.2)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
@@ -269,8 +269,8 @@ GEM
|
||||
grpc (~> 1.36)
|
||||
geocoder (1.8.1)
|
||||
gli (2.21.0)
|
||||
globalid (1.2.1)
|
||||
activesupport (>= 6.1)
|
||||
globalid (1.1.0)
|
||||
activesupport (>= 5.0)
|
||||
gmail_xoauth (0.4.2)
|
||||
oauth (>= 0.3.6)
|
||||
google-apis-core (0.11.0)
|
||||
@@ -450,9 +450,9 @@ GEM
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2023.0218.1)
|
||||
mini_magick (4.12.0)
|
||||
mini_mime (1.1.5)
|
||||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.8.4)
|
||||
minitest (5.20.0)
|
||||
minitest (5.19.0)
|
||||
mock_redis (0.36.0)
|
||||
ruby2_keywords
|
||||
msgpack (1.7.0)
|
||||
@@ -463,7 +463,7 @@ GEM
|
||||
activerecord (>= 5.2)
|
||||
net-http-persistent (4.0.2)
|
||||
connection_pool (~> 2.2)
|
||||
net-imap (0.3.7)
|
||||
net-imap (0.3.6)
|
||||
date
|
||||
net-protocol
|
||||
net-pop (0.1.2)
|
||||
@@ -478,14 +478,14 @@ GEM
|
||||
sidekiq
|
||||
newrelic_rpm (8.16.0)
|
||||
nio4r (2.5.9)
|
||||
nokogiri (1.15.4)
|
||||
nokogiri (1.15.3)
|
||||
mini_portile2 (~> 2.8.2)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.15.4-arm64-darwin)
|
||||
nokogiri (1.15.3-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.15.4-x86_64-darwin)
|
||||
nokogiri (1.15.3-x86_64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.15.4-x86_64-linux)
|
||||
nokogiri (1.15.3-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
numo-narray (0.9.2.1)
|
||||
oauth (1.1.0)
|
||||
@@ -563,30 +563,30 @@ GEM
|
||||
rack-test (2.1.0)
|
||||
rack (>= 1.3)
|
||||
rack-timeout (0.6.3)
|
||||
rails (7.0.8)
|
||||
actioncable (= 7.0.8)
|
||||
actionmailbox (= 7.0.8)
|
||||
actionmailer (= 7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
actiontext (= 7.0.8)
|
||||
actionview (= 7.0.8)
|
||||
activejob (= 7.0.8)
|
||||
activemodel (= 7.0.8)
|
||||
activerecord (= 7.0.8)
|
||||
activestorage (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
rails (7.0.7.2)
|
||||
actioncable (= 7.0.7.2)
|
||||
actionmailbox (= 7.0.7.2)
|
||||
actionmailer (= 7.0.7.2)
|
||||
actionpack (= 7.0.7.2)
|
||||
actiontext (= 7.0.7.2)
|
||||
actionview (= 7.0.7.2)
|
||||
activejob (= 7.0.7.2)
|
||||
activemodel (= 7.0.7.2)
|
||||
activerecord (= 7.0.7.2)
|
||||
activestorage (= 7.0.7.2)
|
||||
activesupport (= 7.0.7.2)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 7.0.8)
|
||||
rails-dom-testing (2.2.0)
|
||||
railties (= 7.0.7.2)
|
||||
rails-dom-testing (2.1.1)
|
||||
activesupport (>= 5.0.0)
|
||||
minitest
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.6.0)
|
||||
loofah (~> 2.21)
|
||||
nokogiri (~> 1.14)
|
||||
railties (7.0.8)
|
||||
actionpack (= 7.0.8)
|
||||
activesupport (= 7.0.8)
|
||||
railties (7.0.7.2)
|
||||
actionpack (= 7.0.7.2)
|
||||
activesupport (= 7.0.7.2)
|
||||
method_source
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
@@ -598,7 +598,7 @@ GEM
|
||||
ffi (~> 1.0)
|
||||
redis (5.0.6)
|
||||
redis-client (>= 0.9.0)
|
||||
redis-client (0.17.0)
|
||||
redis-client (0.14.1)
|
||||
connection_pool
|
||||
redis-namespace (1.10.0)
|
||||
redis (>= 4)
|
||||
@@ -697,23 +697,23 @@ GEM
|
||||
activesupport (>= 4)
|
||||
selectize-rails (0.12.6)
|
||||
semantic_range (3.0.0)
|
||||
sentry-rails (5.11.0)
|
||||
sentry-rails (5.10.0)
|
||||
railties (>= 5.0)
|
||||
sentry-ruby (~> 5.11.0)
|
||||
sentry-ruby (5.11.0)
|
||||
sentry-ruby (~> 5.10.0)
|
||||
sentry-ruby (5.10.0)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
sentry-sidekiq (5.11.0)
|
||||
sentry-ruby (~> 5.11.0)
|
||||
sentry-sidekiq (5.10.0)
|
||||
sentry-ruby (~> 5.10.0)
|
||||
sidekiq (>= 3.0)
|
||||
sexp_processor (4.17.0)
|
||||
shoulda-matchers (5.3.0)
|
||||
activesupport (>= 5.2.0)
|
||||
sidekiq (7.1.3)
|
||||
sidekiq (7.1.2)
|
||||
concurrent-ruby (< 2)
|
||||
connection_pool (>= 2.3.0)
|
||||
rack (>= 2.2.4)
|
||||
redis-client (>= 0.14.0)
|
||||
sidekiq-cron (1.10.1)
|
||||
sidekiq-cron (1.10.0)
|
||||
fugit (~> 1.8)
|
||||
globalid (>= 1.0.1)
|
||||
sidekiq (>= 6)
|
||||
@@ -786,7 +786,7 @@ GEM
|
||||
version_gem (1.1.2)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
web-console (4.2.1)
|
||||
web-console (4.2.0)
|
||||
actionview (>= 6.0.0)
|
||||
activemodel (>= 6.0.0)
|
||||
bindex (>= 0.4.0)
|
||||
@@ -805,14 +805,14 @@ GEM
|
||||
railties (>= 5.2)
|
||||
semantic_range (>= 2.3.0)
|
||||
webrick (1.8.1)
|
||||
websocket-driver (0.7.6)
|
||||
websocket-driver (0.7.5)
|
||||
websocket-extensions (>= 0.1.0)
|
||||
websocket-extensions (0.1.5)
|
||||
wisper (2.0.0)
|
||||
working_hours (1.4.1)
|
||||
activesupport (>= 3.2)
|
||||
tzinfo
|
||||
zeitwerk (2.6.11)
|
||||
zeitwerk (2.6.9)
|
||||
|
||||
PLATFORMS
|
||||
arm64-darwin-20
|
||||
@@ -907,7 +907,7 @@ DEPENDENCIES
|
||||
rack-cors
|
||||
rack-mini-profiler (>= 3.1.1)
|
||||
rack-timeout
|
||||
rails (~> 7.0.8.0)
|
||||
rails (~> 7.0.7.2)
|
||||
redis
|
||||
redis-namespace
|
||||
responders
|
||||
@@ -922,12 +922,12 @@ DEPENDENCIES
|
||||
scout_apm
|
||||
scss_lint
|
||||
seed_dump
|
||||
sentry-rails (>= 5.11.0)
|
||||
sentry-rails (>= 5.10.0)
|
||||
sentry-ruby
|
||||
sentry-sidekiq (>= 5.11.0)
|
||||
sentry-sidekiq (>= 5.10.0)
|
||||
shoulda-matchers
|
||||
sidekiq (>= 7.1.3)
|
||||
sidekiq-cron (>= 1.10.1)
|
||||
sidekiq
|
||||
sidekiq-cron
|
||||
simplecov (= 0.17.1)
|
||||
slack-ruby-client (~> 2.0.0)
|
||||
spring
|
||||
@@ -943,7 +943,7 @@ DEPENDENCIES
|
||||
tzinfo-data
|
||||
uglifier
|
||||
valid_email2
|
||||
web-console (>= 4.2.1)
|
||||
web-console
|
||||
web-push
|
||||
webmock
|
||||
webpacker
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
backend: bin/rails s -p 3000
|
||||
frontend: export NODE_OPTIONS=--openssl-legacy-provider && bin/webpack-dev-server
|
||||
frontend: bin/webpack-dev-server
|
||||
# https://github.com/mperham/sidekiq/issues/3090#issuecomment-389748695
|
||||
worker: dotenv bundle exec sidekiq -C config/sidekiq.yml
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
3.1.0
|
||||
2.17.0
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2.4.0
|
||||
2.3.0
|
||||
|
||||
@@ -60,9 +60,6 @@
|
||||
],
|
||||
"stack": "heroku-20",
|
||||
"buildpacks": [
|
||||
{
|
||||
"url": "heroku/nodejs"
|
||||
},
|
||||
{
|
||||
"url": "heroku/ruby"
|
||||
}
|
||||
|
||||
@@ -29,5 +29,3 @@
|
||||
@import 'components/pagination';
|
||||
@import 'components/search';
|
||||
@import 'components/reports';
|
||||
|
||||
@import 'custom_styles';
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
// custom styles for the dashboard
|
||||
|
||||
.feature-cell {
|
||||
background: $color-extra-light-blue;
|
||||
border-radius: 10px;
|
||||
float: left;
|
||||
margin-left: 8px;
|
||||
margin-top: 6px;
|
||||
padding: 4px 12px;
|
||||
|
||||
.icon-container {
|
||||
margin-right: 4px;
|
||||
|
||||
}
|
||||
|
||||
.value-container {
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.feature-container {
|
||||
max-width: 100rem;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController
|
||||
before_action :portal
|
||||
before_action :check_authorization
|
||||
before_action :fetch_article, except: [:index, :create, :reorder]
|
||||
before_action :fetch_article, except: [:index, :create, :attach_file, :reorder]
|
||||
before_action :set_current_page, only: [:index]
|
||||
|
||||
def index
|
||||
@@ -36,6 +36,17 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController
|
||||
head :ok
|
||||
end
|
||||
|
||||
def attach_file
|
||||
file_blob = ActiveStorage::Blob.create_and_upload!(
|
||||
key: nil,
|
||||
io: params[:background_image].tempfile,
|
||||
filename: params[:background_image].original_filename,
|
||||
content_type: params[:background_image].content_type
|
||||
)
|
||||
file_blob.save!
|
||||
render json: { file_url: url_for(file_blob) }
|
||||
end
|
||||
|
||||
def reorder
|
||||
Article.update_positions(params[:positions_hash])
|
||||
head :ok
|
||||
|
||||
@@ -20,6 +20,16 @@ class Api::V1::Accounts::AutomationRulesController < Api::V1::Accounts::BaseCont
|
||||
@automation_rule
|
||||
end
|
||||
|
||||
def attach_file
|
||||
file_blob = ActiveStorage::Blob.create_and_upload!(
|
||||
key: nil,
|
||||
io: params[:attachment].tempfile,
|
||||
filename: params[:attachment].original_filename,
|
||||
content_type: params[:attachment].content_type
|
||||
)
|
||||
render json: { blob_key: file_blob.key, blob_id: file_blob.id }
|
||||
end
|
||||
|
||||
def update
|
||||
ActiveRecord::Base.transaction do
|
||||
automation_rule_update
|
||||
|
||||
@@ -18,7 +18,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||
|
||||
def index
|
||||
@contacts_count = resolved_contacts.count
|
||||
@contacts = fetch_contacts(resolved_contacts)
|
||||
@contacts = fetch_contacts_with_conversation_count(resolved_contacts)
|
||||
end
|
||||
|
||||
def search
|
||||
@@ -29,7 +29,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||
search: "%#{params[:q].strip}%"
|
||||
)
|
||||
@contacts_count = contacts.count
|
||||
@contacts = fetch_contacts(contacts)
|
||||
@contacts = fetch_contacts_with_conversation_count(contacts)
|
||||
end
|
||||
|
||||
def import
|
||||
@@ -63,7 +63,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||
result = ::Contacts::FilterService.new(params.permit!, current_user).perform
|
||||
contacts = result[:contacts]
|
||||
@contacts_count = result[:count]
|
||||
@contacts = fetch_contacts(contacts)
|
||||
@contacts = fetch_contacts_with_conversation_count(contacts)
|
||||
end
|
||||
|
||||
def contactable_inboxes
|
||||
@@ -125,14 +125,17 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||
@current_page = params[:page] || 1
|
||||
end
|
||||
|
||||
def fetch_contacts(contacts)
|
||||
contacts_with_avatar = filtrate(contacts)
|
||||
.includes([{ avatar_attachment: [:blob] }])
|
||||
.page(@current_page).per(RESULTS_PER_PAGE)
|
||||
def fetch_contacts_with_conversation_count(contacts)
|
||||
conversation_count_sub_query = 'SELECT COUNT(*) FROM "conversations" WHERE "conversations"."contact_id" = "contacts"."id"'
|
||||
contacts_with_conversation_count = filtrate(contacts)
|
||||
.select("contacts.*, (#{conversation_count_sub_query}) as conversations_count")
|
||||
.group('contacts.id')
|
||||
.includes([{ avatar_attachment: [:blob] }])
|
||||
.page(@current_page).per(RESULTS_PER_PAGE)
|
||||
|
||||
return contacts_with_avatar.includes([{ contact_inboxes: [:inbox] }]) if @include_contact_inboxes
|
||||
return contacts_with_conversation_count.includes([{ contact_inboxes: [:inbox] }]) if @include_contact_inboxes
|
||||
|
||||
contacts_with_avatar
|
||||
contacts_with_conversation_count
|
||||
end
|
||||
|
||||
def build_contact_inbox
|
||||
|
||||
@@ -39,6 +39,16 @@ class Api::V1::Accounts::MacrosController < Api::V1::Accounts::BaseController
|
||||
head :ok
|
||||
end
|
||||
|
||||
def attach_file
|
||||
file_blob = ActiveStorage::Blob.create_and_upload!(
|
||||
key: nil,
|
||||
io: params[:attachment].tempfile,
|
||||
filename: params[:attachment].original_filename,
|
||||
content_type: params[:attachment].content_type
|
||||
)
|
||||
render json: { blob_key: file_blob.key, blob_id: file_blob.id }
|
||||
end
|
||||
|
||||
def execute
|
||||
::MacrosExecutionJob.perform_later(@macro, conversation_ids: params[:conversation_ids], user: Current.user)
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
|
||||
include ::FileTypeHelper
|
||||
|
||||
before_action :fetch_portal, except: [:index, :create]
|
||||
before_action :fetch_portal, except: [:index, :create, :attach_file]
|
||||
before_action :check_authorization
|
||||
before_action :set_current_page, only: [:index]
|
||||
|
||||
@@ -53,6 +53,16 @@ class Api::V1::Accounts::PortalsController < Api::V1::Accounts::BaseController
|
||||
@portal.logo.attach(blob)
|
||||
end
|
||||
|
||||
def attach_file
|
||||
file_blob = ActiveStorage::Blob.create_and_upload!(
|
||||
key: nil,
|
||||
io: params[:logo].tempfile,
|
||||
filename: params[:logo].original_filename,
|
||||
content_type: params[:logo].content_type
|
||||
)
|
||||
render json: { blob_key: file_blob.key, blob_id: file_blob.id }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fetch_portal
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
class Api::V1::Accounts::UploadController < Api::V1::Accounts::BaseController
|
||||
def create
|
||||
file_blob = ActiveStorage::Blob.create_and_upload!(
|
||||
key: nil,
|
||||
io: params[:attachment].tempfile,
|
||||
filename: params[:attachment].original_filename,
|
||||
content_type: params[:attachment].content_type
|
||||
)
|
||||
file_blob.save!
|
||||
|
||||
render json: { file_url: url_for(file_blob), blob_key: file_blob.key, blob_id: file_blob.id }
|
||||
end
|
||||
end
|
||||
@@ -8,7 +8,7 @@ module EnsureCurrentAccountHelper
|
||||
|
||||
def ensure_current_account
|
||||
account = Account.find(params[:account_id])
|
||||
render_unauthorized('Account is suspended') and return unless account.active?
|
||||
ensure_account_is_active?(account)
|
||||
|
||||
if current_user
|
||||
account_accessible_for_user?(account)
|
||||
@@ -27,4 +27,8 @@ module EnsureCurrentAccountHelper
|
||||
def account_accessible_for_bot?(account)
|
||||
render_unauthorized('You are not authorized to access this account') unless @resource.agent_bot_inboxes.find_by(account_id: account.id)
|
||||
end
|
||||
|
||||
def ensure_account_is_active?(account)
|
||||
render_unauthorized('Account is suspended') unless account.active?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
class Public::Api::V1::Portals::BaseController < PublicController
|
||||
before_action :show_plain_layout
|
||||
around_action :set_locale
|
||||
after_action :allow_iframe_requests
|
||||
|
||||
private
|
||||
|
||||
@@ -40,8 +39,4 @@ class Public::Api::V1::Portals::BaseController < PublicController
|
||||
|
||||
I18n.with_locale(@locale, &)
|
||||
end
|
||||
|
||||
def allow_iframe_requests
|
||||
response.headers.delete('X-Frame-Options') if @is_plain_layout_enabled
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,7 +6,6 @@ class SuperAdmin::InstanceStatusesController < SuperAdmin::ApplicationController
|
||||
postgres_status
|
||||
redis_metrics
|
||||
chatwoot_edition
|
||||
instance_meta
|
||||
end
|
||||
|
||||
def chatwoot_edition
|
||||
@@ -19,10 +18,6 @@ class SuperAdmin::InstanceStatusesController < SuperAdmin::ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def instance_meta
|
||||
@metrics['Database Migrations'] = ActiveRecord::Base.connection.migration_context.needs_migration? ? 'pending' : 'completed'
|
||||
end
|
||||
|
||||
def chatwoot_version
|
||||
@metrics['Chatwoot version'] = Chatwoot.config[:version]
|
||||
end
|
||||
|
||||
@@ -9,6 +9,14 @@ class AutomationsAPI extends ApiClient {
|
||||
clone(automationId) {
|
||||
return axios.post(`${this.url}/${automationId}/clone`);
|
||||
}
|
||||
|
||||
attachment(file) {
|
||||
return axios.post(`${this.url}/attach_file`, file, {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new AutomationsAPI();
|
||||
|
||||
@@ -47,6 +47,20 @@ class ArticlesAPI extends PortalsAPI {
|
||||
return axios.delete(`${this.url}/${portalSlug}/articles/${articleId}`);
|
||||
}
|
||||
|
||||
uploadImage({ portalSlug, file }) {
|
||||
let formData = new FormData();
|
||||
formData.append('background_image', file);
|
||||
return axios.post(
|
||||
`${this.url}/${portalSlug}/articles/attach_file`,
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
reorderArticles({ portalSlug, reorderedGroup, categorySlug }) {
|
||||
return axios.post(`${this.url}/${portalSlug}/articles/reorder`, {
|
||||
positions_hash: reorderedGroup,
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
>
|
||||
<a
|
||||
v-if="isAttributeTypeLink"
|
||||
:href="hrefURL"
|
||||
:href="value"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="value inline-block rounded-sm mb-0 break-all py-0.5 px-1"
|
||||
@@ -188,9 +188,6 @@ export default {
|
||||
urlValue() {
|
||||
return isValidURL(this.value) ? this.value : '---';
|
||||
},
|
||||
hrefURL() {
|
||||
return isValidURL(this.value) ? this.value : '';
|
||||
},
|
||||
notAttributeTypeCheckboxAndList() {
|
||||
return !this.isAttributeTypeCheckbox && !this.isAttributeTypeList;
|
||||
},
|
||||
|
||||
@@ -61,9 +61,11 @@ export default {
|
||||
this.label = this.$t('AUTOMATION.ATTACHMENT.LABEL_UPLOADING');
|
||||
try {
|
||||
const file = event.target.files[0];
|
||||
const formData = new FormData();
|
||||
formData.append('attachment', file, file.name);
|
||||
const id = await this.$store.dispatch(
|
||||
'automations/uploadAttachment',
|
||||
file
|
||||
formData
|
||||
);
|
||||
this.$emit('input', [id]);
|
||||
this.uploadState = 'uploaded';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="flex items-center gap-1.5 text-left">
|
||||
<div class="flex items-center text-left">
|
||||
<thumbnail
|
||||
:src="user.thumbnail"
|
||||
:size="size"
|
||||
@@ -7,7 +7,7 @@
|
||||
:status="user.availability_status"
|
||||
/>
|
||||
<h6
|
||||
class="my-0 dark:text-slate-100 overflow-hidden whitespace-nowrap text-ellipsis text-capitalize"
|
||||
class="my-0 mx-2 dark:text-slate-100 overflow-hidden whitespace-nowrap text-ellipsis text-capitalize"
|
||||
:class="textClass"
|
||||
>
|
||||
{{ user.name }}
|
||||
|
||||
@@ -15,13 +15,6 @@
|
||||
:search-key="variableSearchTerm"
|
||||
@click="insertVariable"
|
||||
/>
|
||||
<input
|
||||
ref="imageUpload"
|
||||
type="file"
|
||||
accept="image/png, image/jpeg, image/jpg, image/gif, image/webp"
|
||||
hidden
|
||||
@change="onFileChange"
|
||||
/>
|
||||
<div ref="editor" />
|
||||
</div>
|
||||
</template>
|
||||
@@ -29,7 +22,7 @@
|
||||
<script>
|
||||
import {
|
||||
messageSchema,
|
||||
buildEditor,
|
||||
wootMessageWriterSetup,
|
||||
EditorView,
|
||||
MessageMarkdownTransformer,
|
||||
MessageMarkdownSerializer,
|
||||
@@ -44,13 +37,8 @@ import {
|
||||
import TagAgents from '../conversation/TagAgents';
|
||||
import CannedResponse from '../conversation/CannedResponse';
|
||||
import VariableList from '../conversation/VariableList';
|
||||
import {
|
||||
appendSignature,
|
||||
removeSignature,
|
||||
} from 'dashboard/helper/editorHelper';
|
||||
|
||||
const TYPING_INDICATOR_IDLE_TIME = 4000;
|
||||
const MAXIMUM_FILE_UPLOAD_SIZE = 4; // in MB
|
||||
|
||||
import {
|
||||
hasPressedEnterAndNotCmdOrShift,
|
||||
@@ -63,27 +51,14 @@ import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||
import { isEditorHotKeyEnabled } from 'dashboard/mixins/uiSettings';
|
||||
import { replaceVariablesInMessage } from '@chatwoot/utils';
|
||||
import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events';
|
||||
import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
|
||||
import { uploadFile } from 'dashboard/helper/uploadHelper';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import { findNodeToInsertImage } from 'dashboard/helper/messageEditorHelper';
|
||||
import { MESSAGE_EDITOR_MENU_OPTIONS } from 'dashboard/constants/editor';
|
||||
|
||||
const createState = (
|
||||
content,
|
||||
placeholder,
|
||||
plugins = [],
|
||||
methods = {},
|
||||
enabledMenuOptions
|
||||
) => {
|
||||
const createState = (content, placeholder, plugins = []) => {
|
||||
return EditorState.create({
|
||||
doc: new MessageMarkdownTransformer(messageSchema).parse(content),
|
||||
plugins: buildEditor({
|
||||
plugins: wootMessageWriterSetup({
|
||||
schema: messageSchema,
|
||||
placeholder,
|
||||
methods,
|
||||
plugins,
|
||||
enabledMenuOptions,
|
||||
}),
|
||||
});
|
||||
};
|
||||
@@ -91,7 +66,7 @@ const createState = (
|
||||
export default {
|
||||
name: 'WootMessageEditor',
|
||||
components: { TagAgents, CannedResponse, VariableList },
|
||||
mixins: [eventListenerMixins, uiSettingsMixin, alertMixin],
|
||||
mixins: [eventListenerMixins, uiSettingsMixin],
|
||||
props: {
|
||||
value: { type: String, default: '' },
|
||||
editorId: { type: String, default: '' },
|
||||
@@ -103,11 +78,6 @@ export default {
|
||||
enableVariables: { type: Boolean, default: false },
|
||||
enableCannedResponses: { type: Boolean, default: true },
|
||||
variables: { type: Object, default: () => ({}) },
|
||||
enabledMenuOptions: { type: Array, default: () => [] },
|
||||
signature: { type: String, default: '' },
|
||||
// allowSignature is a kill switch, ensuring no signature methods
|
||||
// are triggered except when this flag is true
|
||||
allowSignature: { type: Boolean, default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -134,11 +104,6 @@ export default {
|
||||
this.enableCannedResponses && this.showCannedMenu && !this.isPrivate
|
||||
);
|
||||
},
|
||||
editorMenuOptions() {
|
||||
return this.enabledMenuOptions.length
|
||||
? this.enabledMenuOptions
|
||||
: MESSAGE_EDITOR_MENU_OPTIONS;
|
||||
},
|
||||
plugins() {
|
||||
if (!this.enableSuggestions) {
|
||||
return [];
|
||||
@@ -228,12 +193,6 @@ export default {
|
||||
}),
|
||||
];
|
||||
},
|
||||
sendWithSignature() {
|
||||
// this is considered the source of truth, we watch this property
|
||||
// on change, we toggle the signature in the editor
|
||||
const { send_with_signature: isEnabled } = this.uiSettings;
|
||||
return isEnabled && this.allowSignature && !this.isPrivate;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
showUserMentions(updatedValue) {
|
||||
@@ -245,19 +204,20 @@ export default {
|
||||
showVariables(updatedValue) {
|
||||
this.$emit('toggle-variables-menu', !this.isPrivate && updatedValue);
|
||||
},
|
||||
value(newVal = '') {
|
||||
if (newVal !== this.contentFromEditor) {
|
||||
this.reloadState(newVal);
|
||||
value(newValue = '') {
|
||||
if (newValue !== this.contentFromEditor) {
|
||||
this.reloadState();
|
||||
}
|
||||
},
|
||||
editorId() {
|
||||
this.showCannedMenu = false;
|
||||
this.cannedSearchTerm = '';
|
||||
this.reloadState(this.value);
|
||||
this.reloadState();
|
||||
},
|
||||
isPrivate() {
|
||||
this.reloadState(this.value);
|
||||
this.reloadState();
|
||||
},
|
||||
|
||||
updateSelectionWith(newValue, oldValue) {
|
||||
if (!this.editorView) {
|
||||
return null;
|
||||
@@ -276,106 +236,20 @@ export default {
|
||||
}
|
||||
return null;
|
||||
},
|
||||
sendWithSignature(newValue) {
|
||||
// see if the allowSignature flag is true
|
||||
if (this.allowSignature) {
|
||||
this.toggleSignatureInEditor(newValue);
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.state = createState(
|
||||
this.value,
|
||||
this.placeholder,
|
||||
this.plugins,
|
||||
{ onImageUpload: this.openFileBrowser },
|
||||
this.editorMenuOptions
|
||||
);
|
||||
this.state = createState(this.value, this.placeholder, this.plugins);
|
||||
},
|
||||
mounted() {
|
||||
this.createEditorView();
|
||||
this.editorView.updateState(this.state);
|
||||
this.focusEditor(this.value);
|
||||
this.focusEditorInputField();
|
||||
},
|
||||
methods: {
|
||||
reloadState(content = this.value) {
|
||||
this.state = createState(
|
||||
content,
|
||||
this.placeholder,
|
||||
this.plugins,
|
||||
{ onImageUpload: this.openFileBrowser },
|
||||
this.editorMenuOptions
|
||||
);
|
||||
reloadState() {
|
||||
this.state = createState(this.value, this.placeholder, this.plugins);
|
||||
this.editorView.updateState(this.state);
|
||||
|
||||
this.focusEditor(content);
|
||||
},
|
||||
focusEditor(content) {
|
||||
if (this.isBodyEmpty(content) && this.sendWithSignature) {
|
||||
// reload state can be called when switching between conversations, or when drafts is loaded
|
||||
// these drafts can also have a signature, so we need to check if the body is empty
|
||||
// and handle things accordingly
|
||||
this.handleEmptyBodyWithSignature();
|
||||
} else {
|
||||
// this is in the else block, handleEmptyBodyWithSignature also has a call to the focus method
|
||||
// the position is set to start, because the signature is added at the end of the body
|
||||
this.focusEditorInputField('end');
|
||||
}
|
||||
},
|
||||
toggleSignatureInEditor(signatureEnabled) {
|
||||
// The toggleSignatureInEditor gets the new value from the
|
||||
// watcher, this means that if the value is true, the signature
|
||||
// is supposed to be added, else we remove it.
|
||||
if (signatureEnabled) {
|
||||
this.addSignature();
|
||||
} else {
|
||||
this.removeSignature();
|
||||
}
|
||||
},
|
||||
addSignature() {
|
||||
let content = this.value;
|
||||
// see if the content is empty, if it is before appending the signature
|
||||
// we need to add a paragraph node and move the cursor at the start of the editor
|
||||
const contentWasEmpty = this.isBodyEmpty(content);
|
||||
content = appendSignature(content, this.signature);
|
||||
// need to reload first, ensuring that the editorView is updated
|
||||
this.reloadState(content);
|
||||
|
||||
if (contentWasEmpty) {
|
||||
this.handleEmptyBodyWithSignature();
|
||||
}
|
||||
},
|
||||
removeSignature() {
|
||||
if (!this.signature) return;
|
||||
let content = this.value;
|
||||
content = removeSignature(content, this.signature);
|
||||
// reload the state, ensuring that the editorView is updated
|
||||
this.reloadState(content);
|
||||
},
|
||||
isBodyEmpty(content) {
|
||||
// if content is undefined, we assume that the body is empty
|
||||
if (!content) return true;
|
||||
|
||||
// if the signature is present, we need to remove it before checking
|
||||
// note that we don't update the editorView, so this is safe
|
||||
const bodyWithoutSignature = this.signature
|
||||
? removeSignature(content, this.signature)
|
||||
: content;
|
||||
|
||||
// trimming should remove all the whitespaces, so we can check the length
|
||||
return bodyWithoutSignature.trim().length === 0;
|
||||
},
|
||||
handleEmptyBodyWithSignature() {
|
||||
const { schema, tr } = this.state;
|
||||
|
||||
// create a paragraph node and
|
||||
// start a transaction to append it at the end
|
||||
const paragraph = schema.nodes.paragraph.create();
|
||||
const paragraphTransaction = tr.insert(0, paragraph);
|
||||
this.editorView.dispatch(paragraphTransaction);
|
||||
|
||||
// Set the focus at the start of the input field
|
||||
this.focusEditorInputField('start');
|
||||
this.focusEditorInputField();
|
||||
},
|
||||
createEditorView() {
|
||||
this.editorView = new EditorView(this.$refs.editor, {
|
||||
@@ -420,11 +294,9 @@ export default {
|
||||
this.focusEditorInputField();
|
||||
}
|
||||
},
|
||||
focusEditorInputField(pos = 'end') {
|
||||
focusEditorInputField() {
|
||||
const { tr } = this.editorView.state;
|
||||
|
||||
const selection =
|
||||
pos === 'end' ? Selection.atEnd(tr.doc) : Selection.atStart(tr.doc);
|
||||
const selection = Selection.atEnd(tr.doc);
|
||||
|
||||
this.editorView.dispatch(tr.setSelection(selection));
|
||||
this.editorView.focus();
|
||||
@@ -502,57 +374,6 @@ export default {
|
||||
tr.scrollIntoView();
|
||||
return false;
|
||||
},
|
||||
openFileBrowser() {
|
||||
this.$refs.imageUpload.click();
|
||||
},
|
||||
onFileChange() {
|
||||
const file = this.$refs.imageUpload.files[0];
|
||||
if (checkFileSizeLimit(file, MAXIMUM_FILE_UPLOAD_SIZE)) {
|
||||
this.uploadImageToStorage(file);
|
||||
} else {
|
||||
this.showAlert(
|
||||
this.$t(
|
||||
'PROFILE_SETTINGS.FORM.MESSAGE_SIGNATURE_SECTION.IMAGE_UPLOAD_SIZE_ERROR',
|
||||
{
|
||||
size: MAXIMUM_FILE_UPLOAD_SIZE,
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
this.$refs.imageUpload.value = '';
|
||||
},
|
||||
async uploadImageToStorage(file) {
|
||||
try {
|
||||
const { fileUrl } = await uploadFile(file);
|
||||
if (fileUrl) {
|
||||
this.onImageInsertInEditor(fileUrl);
|
||||
}
|
||||
this.showAlert(
|
||||
this.$t(
|
||||
'PROFILE_SETTINGS.FORM.MESSAGE_SIGNATURE_SECTION.IMAGE_UPLOAD_SUCCESS'
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
this.showAlert(
|
||||
this.$t(
|
||||
'PROFILE_SETTINGS.FORM.MESSAGE_SIGNATURE_SECTION.IMAGE_UPLOAD_ERROR'
|
||||
)
|
||||
);
|
||||
}
|
||||
},
|
||||
onImageInsertInEditor(fileUrl) {
|
||||
const { tr } = this.editorView.state;
|
||||
|
||||
const insertData = findNodeToInsertImage(this.editorView.state, fileUrl);
|
||||
|
||||
if (insertData) {
|
||||
this.editorView.dispatch(
|
||||
tr.insert(insertData.pos, insertData.node).scrollIntoView()
|
||||
);
|
||||
this.focusEditorInputField();
|
||||
}
|
||||
},
|
||||
|
||||
emitOnChange() {
|
||||
this.editorView.updateState(this.state);
|
||||
@@ -670,10 +491,6 @@ export default {
|
||||
|
||||
.ProseMirror-prompt {
|
||||
@apply z-50 bg-slate-25 dark:bg-slate-700 rounded-md border border-solid border-slate-75 dark:border-slate-800;
|
||||
|
||||
h5 {
|
||||
@apply dark:text-slate-25 text-slate-800;
|
||||
}
|
||||
}
|
||||
|
||||
.is-private {
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
<script>
|
||||
import {
|
||||
fullSchema,
|
||||
buildEditor,
|
||||
wootArticleWriterSetup,
|
||||
EditorView,
|
||||
ArticleMarkdownSerializer,
|
||||
ArticleMarkdownTransformer,
|
||||
@@ -33,17 +33,15 @@ const createState = (
|
||||
content,
|
||||
placeholder,
|
||||
plugins = [],
|
||||
methods = {},
|
||||
enabledMenuOptions
|
||||
onImageUpload = () => {}
|
||||
) => {
|
||||
return EditorState.create({
|
||||
doc: new ArticleMarkdownTransformer(fullSchema).parse(content),
|
||||
plugins: buildEditor({
|
||||
plugins: wootArticleWriterSetup({
|
||||
schema: fullSchema,
|
||||
placeholder,
|
||||
methods,
|
||||
plugins,
|
||||
enabledMenuOptions,
|
||||
onImageUpload,
|
||||
}),
|
||||
});
|
||||
};
|
||||
@@ -54,7 +52,6 @@ export default {
|
||||
value: { type: String, default: '' },
|
||||
editorId: { type: String, default: '' },
|
||||
placeholder: { type: String, default: '' },
|
||||
enabledMenuOptions: { type: Array, default: () => [] },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -86,8 +83,7 @@ export default {
|
||||
this.value,
|
||||
this.placeholder,
|
||||
this.plugins,
|
||||
{ onImageUpload: this.openFileBrowser },
|
||||
this.enabledMenuOptions
|
||||
this.openFileBrowser
|
||||
);
|
||||
},
|
||||
mounted() {
|
||||
@@ -156,8 +152,7 @@ export default {
|
||||
this.value,
|
||||
this.placeholder,
|
||||
this.plugins,
|
||||
{ onImageUpload: this.openFileBrowser },
|
||||
this.enabledMenuOptions
|
||||
this.openFileBrowser
|
||||
);
|
||||
this.editorView.updateState(this.state);
|
||||
this.focusEditorInputField();
|
||||
@@ -218,7 +213,7 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '@chatwoot/prosemirror-schema/src/styles/article.scss';
|
||||
@import '~@chatwoot/prosemirror-schema/src/styles/article.scss';
|
||||
|
||||
.ProseMirror-menubar-wrapper {
|
||||
display: flex;
|
||||
|
||||
@@ -288,7 +288,7 @@ export default {
|
||||
}
|
||||
},
|
||||
showMessageSignatureButton() {
|
||||
return !this.isOnPrivateNote;
|
||||
return !this.isOnPrivateNote && this.isAnEmailChannel;
|
||||
},
|
||||
sendWithSignature() {
|
||||
const { send_with_signature: isEnabled } = this.uiSettings;
|
||||
|
||||
-1
@@ -358,7 +358,6 @@ export default {
|
||||
applied_filters: this.appliedFilters.map(filter => ({
|
||||
key: filter.attribute_key,
|
||||
operator: filter.filter_operator,
|
||||
query_operator: filter.query_operator,
|
||||
})),
|
||||
});
|
||||
},
|
||||
|
||||
@@ -53,9 +53,6 @@
|
||||
class="input"
|
||||
:placeholder="messagePlaceHolder"
|
||||
:min-height="4"
|
||||
:signature="signatureToApply"
|
||||
:allow-signature="true"
|
||||
:send-with-signature="sendWithSignature"
|
||||
@typing-off="onTypingOff"
|
||||
@typing-on="onTypingOn"
|
||||
@focus="onFocus"
|
||||
@@ -72,8 +69,6 @@
|
||||
:min-height="4"
|
||||
:enable-variables="true"
|
||||
:variables="messageVariables"
|
||||
:signature="signatureToApply"
|
||||
:allow-signature="true"
|
||||
@typing-off="onTypingOff"
|
||||
@typing-on="onTypingOn"
|
||||
@focus="onFocus"
|
||||
@@ -91,10 +86,16 @@
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="isSignatureEnabledForInbox && !isSignatureAvailable"
|
||||
v-if="isSignatureEnabledForInbox"
|
||||
v-tooltip="$t('CONVERSATION.FOOTER.MESSAGE_SIGN_TOOLTIP')"
|
||||
class="message-signature-wrap"
|
||||
>
|
||||
<p class="message-signature">
|
||||
<p
|
||||
v-if="isSignatureAvailable"
|
||||
v-dompurify-html="formatMessage(messageSignature)"
|
||||
class="message-signature"
|
||||
/>
|
||||
<p v-else class="message-signature">
|
||||
{{ $t('CONVERSATION.FOOTER.MESSAGE_SIGNATURE_NOT_CONFIGURED') }}
|
||||
<router-link :to="profilePath">
|
||||
{{ $t('CONVERSATION.FOOTER.CLICK_HERE') }}
|
||||
@@ -183,12 +184,6 @@ import wootConstants from 'dashboard/constants/globals';
|
||||
import { isEditorHotKeyEnabled } from 'dashboard/mixins/uiSettings';
|
||||
import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events';
|
||||
import rtlMixin from 'shared/mixins/rtlMixin';
|
||||
import {
|
||||
appendSignature,
|
||||
removeSignature,
|
||||
replaceSignature,
|
||||
extractTextFromMarkdown,
|
||||
} from 'dashboard/helper/editorHelper';
|
||||
|
||||
const EmojiInput = () => import('shared/components/emoji/EmojiInput');
|
||||
|
||||
@@ -476,10 +471,10 @@ export default {
|
||||
);
|
||||
},
|
||||
isSignatureEnabledForInbox() {
|
||||
return !this.isPrivate && this.sendWithSignature;
|
||||
return !this.isPrivate && this.isAnEmailChannel && this.sendWithSignature;
|
||||
},
|
||||
isSignatureAvailable() {
|
||||
return !!this.signatureToApply;
|
||||
return !!this.messageSignature;
|
||||
},
|
||||
sendWithSignature() {
|
||||
const { send_with_signature: isEnabled } = this.uiSettings;
|
||||
@@ -519,12 +514,6 @@ export default {
|
||||
});
|
||||
return variables;
|
||||
},
|
||||
// ensure that the signature is plain text depending on `showRichContentEditor`
|
||||
signatureToApply() {
|
||||
return this.showRichContentEditor
|
||||
? this.messageSignature
|
||||
: extractTextFromMarkdown(this.messageSignature);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
currentChat(conversation) {
|
||||
@@ -592,23 +581,6 @@ export default {
|
||||
this.updateUISettings({
|
||||
display_rich_content_editor: !this.showRichContentEditor,
|
||||
});
|
||||
|
||||
const plainTextSignature = extractTextFromMarkdown(this.messageSignature);
|
||||
|
||||
if (!this.showRichContentEditor && this.messageSignature) {
|
||||
// remove the old signature -> extract text from markdown -> attach new signature
|
||||
let message = removeSignature(this.message, this.messageSignature);
|
||||
message = extractTextFromMarkdown(message);
|
||||
message = appendSignature(message, plainTextSignature);
|
||||
|
||||
this.message = message;
|
||||
} else {
|
||||
this.message = replaceSignature(
|
||||
this.message,
|
||||
plainTextSignature,
|
||||
this.messageSignature
|
||||
);
|
||||
}
|
||||
},
|
||||
saveDraft(conversationId, replyType) {
|
||||
if (this.message || this.message === '') {
|
||||
@@ -628,22 +600,9 @@ export default {
|
||||
getFromDraft() {
|
||||
if (this.conversationIdByRoute) {
|
||||
const key = `draft-${this.conversationIdByRoute}-${this.replyType}`;
|
||||
const messageFromStore =
|
||||
this.$store.getters['draftMessages/get'](key) || '';
|
||||
|
||||
// ensure that the message has signature set based on the ui setting
|
||||
this.message = this.toggleSignatureForDraft(messageFromStore);
|
||||
this.message = this.$store.getters['draftMessages/get'](key) || '';
|
||||
}
|
||||
},
|
||||
toggleSignatureForDraft(message) {
|
||||
if (this.isPrivate) {
|
||||
return message;
|
||||
}
|
||||
|
||||
return this.sendWithSignature
|
||||
? appendSignature(message, this.signatureToApply)
|
||||
: removeSignature(message, this.signatureToApply);
|
||||
},
|
||||
removeFromDraft() {
|
||||
if (this.conversationIdByRoute) {
|
||||
const key = `draft-${this.conversationIdByRoute}-${this.replyType}`;
|
||||
@@ -735,14 +694,19 @@ export default {
|
||||
return;
|
||||
}
|
||||
if (!this.showMentions) {
|
||||
let newMessage = this.message;
|
||||
if (this.isSignatureEnabledForInbox && this.messageSignature) {
|
||||
newMessage += '\n\n' + this.messageSignature;
|
||||
}
|
||||
|
||||
const isOnWhatsApp =
|
||||
this.isATwilioWhatsAppChannel ||
|
||||
this.isAWhatsAppCloudChannel ||
|
||||
this.is360DialogWhatsAppChannel;
|
||||
if (isOnWhatsApp && !this.isPrivate) {
|
||||
this.sendMessageAsMultipleMessages(this.message);
|
||||
this.sendMessageAsMultipleMessages(newMessage);
|
||||
} else {
|
||||
const messagePayload = this.getMessagePayload(this.message);
|
||||
const messagePayload = this.getMessagePayload(newMessage);
|
||||
this.sendMessage(messagePayload);
|
||||
}
|
||||
|
||||
@@ -864,10 +828,6 @@ export default {
|
||||
},
|
||||
clearMessage() {
|
||||
this.message = '';
|
||||
if (this.sendWithSignature && !this.isPrivate) {
|
||||
// if signature is enabled, append it to the message
|
||||
this.message = appendSignature(this.message, this.signatureToApply);
|
||||
}
|
||||
this.attachedFiles = [];
|
||||
this.isRecordingAudio = false;
|
||||
},
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
export const MESSAGE_EDITOR_MENU_OPTIONS = [
|
||||
'strong',
|
||||
'em',
|
||||
'link',
|
||||
'undo',
|
||||
'redo',
|
||||
'bulletList',
|
||||
'orderedList',
|
||||
'code',
|
||||
];
|
||||
|
||||
export const MESSAGE_SIGNATURE_EDITOR_MENU_OPTIONS = [
|
||||
'strong',
|
||||
'em',
|
||||
'link',
|
||||
'undo',
|
||||
'redo',
|
||||
'imageUpload',
|
||||
];
|
||||
|
||||
export const ARTICLE_EDITOR_MENU_OPTIONS = [
|
||||
'strong',
|
||||
'em',
|
||||
'link',
|
||||
'undo',
|
||||
'redo',
|
||||
'bulletList',
|
||||
'orderedList',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'imageUpload',
|
||||
'code',
|
||||
];
|
||||
@@ -120,18 +120,9 @@ export const generateConditionOptions = (options, key = 'id') => {
|
||||
});
|
||||
};
|
||||
|
||||
// Add the "None" option to the agent list
|
||||
export const agentList = agents => [
|
||||
{
|
||||
id: 'nil',
|
||||
name: 'None',
|
||||
},
|
||||
...(agents || []),
|
||||
];
|
||||
|
||||
export const getActionOptions = ({ agents, teams, labels, type }) => {
|
||||
const actionsMap = {
|
||||
assign_agent: agentList(agents),
|
||||
assign_agent: agents,
|
||||
assign_team: teams,
|
||||
send_email_to_team: teams,
|
||||
add_label: generateConditionOptions(labels, 'title'),
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
import {
|
||||
messageSchema,
|
||||
MessageMarkdownTransformer,
|
||||
MessageMarkdownSerializer,
|
||||
} from '@chatwoot/prosemirror-schema';
|
||||
|
||||
/**
|
||||
* The delimiter used to separate the signature from the rest of the body.
|
||||
* @type {string}
|
||||
*/
|
||||
export const SIGNATURE_DELIMITER = '--';
|
||||
|
||||
/**
|
||||
* Parse and Serialize the markdown text to remove any extra spaces or new lines
|
||||
*/
|
||||
export function cleanSignature(signature) {
|
||||
// convert from markdown to common mark format
|
||||
const nodes = new MessageMarkdownTransformer(messageSchema).parse(signature);
|
||||
return MessageMarkdownSerializer.serialize(nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the signature delimiter to the beginning of the signature.
|
||||
*
|
||||
* @param {string} signature - The signature to add the delimiter to.
|
||||
* @returns {string} - The signature with the delimiter added.
|
||||
*/
|
||||
function appendDelimiter(signature) {
|
||||
return `${SIGNATURE_DELIMITER}\n\n${cleanSignature(signature)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there's an unedited signature at the end of the body
|
||||
* If there is, return the index of the signature, If there isn't, return -1
|
||||
*
|
||||
* @param {string} body - The body to search for the signature.
|
||||
* @param {string} signature - The signature to search for.
|
||||
* @returns {number} - The index of the last occurrence of the signature in the body, or -1 if not found.
|
||||
*/
|
||||
export function findSignatureInBody(body, signature) {
|
||||
const trimmedBody = body.trimEnd();
|
||||
const cleanedSignature = cleanSignature(signature);
|
||||
|
||||
// check if body ends with signature
|
||||
if (trimmedBody.endsWith(cleanedSignature)) {
|
||||
return body.lastIndexOf(cleanedSignature);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the signature to the body, separated by the signature delimiter.
|
||||
*
|
||||
* @param {string} body - The body to append the signature to.
|
||||
* @param {string} signature - The signature to append.
|
||||
* @returns {string} - The body with the signature appended.
|
||||
*/
|
||||
export function appendSignature(body, signature) {
|
||||
const cleanedSignature = cleanSignature(signature);
|
||||
// if signature is already present, return body
|
||||
if (findSignatureInBody(body, cleanedSignature) > -1) {
|
||||
return body;
|
||||
}
|
||||
|
||||
return `${body.trimEnd()}\n\n${appendDelimiter(cleanedSignature)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the signature from the body, along with the signature delimiter.
|
||||
*
|
||||
* @param {string} body - The body to remove the signature from.
|
||||
* @param {string} signature - The signature to remove.
|
||||
* @returns {string} - The body with the signature removed.
|
||||
*/
|
||||
export function removeSignature(body, signature) {
|
||||
// this will find the index of the signature if it exists
|
||||
// Regardless of extra spaces or new lines after the signature, the index will be the same if present
|
||||
const cleanedSignature = cleanSignature(signature);
|
||||
const signatureIndex = findSignatureInBody(body, cleanedSignature);
|
||||
|
||||
// no need to trim the ends here, because it will simply be removed in the next method
|
||||
let newBody = body;
|
||||
|
||||
// if signature is present, remove it and trim it
|
||||
// trimming will ensure any spaces or new lines before the signature are removed
|
||||
// This means we will have the delimiter at the end
|
||||
if (signatureIndex > -1) {
|
||||
newBody = newBody.substring(0, signatureIndex).trimEnd();
|
||||
}
|
||||
|
||||
// let's find the delimiter and remove it
|
||||
const delimiterIndex = newBody.lastIndexOf(SIGNATURE_DELIMITER);
|
||||
if (
|
||||
delimiterIndex !== -1 &&
|
||||
delimiterIndex === newBody.length - SIGNATURE_DELIMITER.length // this will ensure the delimiter is at the end
|
||||
) {
|
||||
// if the delimiter is at the end, remove it
|
||||
newBody = newBody.substring(0, delimiterIndex);
|
||||
}
|
||||
|
||||
// return the value
|
||||
return newBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the old signature with the new signature.
|
||||
* If the old signature is not present, it will append the new signature.
|
||||
*
|
||||
* @param {string} body - The body to replace the signature in.
|
||||
* @param {string} oldSignature - The signature to replace.
|
||||
* @param {string} newSignature - The signature to replace the old signature with.
|
||||
* @returns {string} - The body with the old signature replaced with the new signature.
|
||||
*
|
||||
*/
|
||||
export function replaceSignature(body, oldSignature, newSignature) {
|
||||
const withoutSignature = removeSignature(body, oldSignature);
|
||||
return appendSignature(withoutSignature, newSignature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract text from markdown, and remove all images, code blocks, links, headers, bold, italic, lists etc.
|
||||
* Links will be converted to text, and not removed.
|
||||
*
|
||||
* @param {string} markdown - markdown text to be extracted
|
||||
* @returns
|
||||
*/
|
||||
export function extractTextFromMarkdown(markdown) {
|
||||
return markdown
|
||||
.replace(/```[\s\S]*?```/g, '') // Remove code blocks
|
||||
.replace(/`.*?`/g, '') // Remove inline code
|
||||
.replace(/!\[.*?\]\(.*?\)/g, '') // Remove images before removing links
|
||||
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Remove links but keep the text
|
||||
.replace(/#+\s*|[*_-]{1,3}/g, '') // Remove headers, bold, italic, lists etc.
|
||||
.split('\n')
|
||||
.map(line => line.trim())
|
||||
.filter(Boolean)
|
||||
.join('\n') // Trim each line & remove any lines only having spaces
|
||||
.replace(/\n{2,}/g, '\n') // Remove multiple consecutive newlines (blank lines)
|
||||
.trim(); // Trim any extra space
|
||||
}
|
||||
@@ -1,34 +1,21 @@
|
||||
const setArrayValues = item => {
|
||||
return item.values[0]?.id ? item.values.map(val => val.id) : item.values;
|
||||
};
|
||||
|
||||
const generateValues = item => {
|
||||
if (item.attribute_key === 'content') {
|
||||
const values = item.values || '';
|
||||
return values.split(',');
|
||||
}
|
||||
if (Array.isArray(item.values)) {
|
||||
return setArrayValues(item);
|
||||
}
|
||||
if (typeof item.values === 'object') {
|
||||
return [item.values.id];
|
||||
}
|
||||
if (!item.values) {
|
||||
return [];
|
||||
}
|
||||
return [item.values];
|
||||
};
|
||||
|
||||
const generatePayload = data => {
|
||||
// Make a copy of data to avoid vue data reactivity issues
|
||||
const filters = JSON.parse(JSON.stringify(data));
|
||||
let payload = filters.map(item => {
|
||||
// If item key is content, we will split it using comma and return as array
|
||||
// FIX ME: Make this generic option instead of using the key directly here
|
||||
item.values = generateValues(item);
|
||||
if (Array.isArray(item.values)) {
|
||||
item.values = setArrayValues(item);
|
||||
} else if (typeof item.values === 'object') {
|
||||
item.values = [item.values.id];
|
||||
} else if (!item.values) {
|
||||
item.values = [];
|
||||
} else {
|
||||
item.values = [item.values];
|
||||
}
|
||||
return item;
|
||||
});
|
||||
|
||||
// For every query added, the query_operator is set default to and so the
|
||||
// last query will have an extra query_operator, this would break the api.
|
||||
// Setting this to null for all query payload
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* Determines the appropriate node and position to insert an image in the editor.
|
||||
*
|
||||
* Based on the current editor state and the provided image URL, this function finds out the correct node (either
|
||||
* a standalone image node or an image wrapped in a paragraph) and its respective position in the editor.
|
||||
*
|
||||
* 1. If the current node is a paragraph and doesn't contain an image or text, the image is inserted directly into it.
|
||||
* 2. If the current node isn't a paragraph or it's a paragraph containing text, the image will be wrapped
|
||||
* in a new paragraph and then inserted.
|
||||
* 3. If the current node is a paragraph containing an image, the new image will be inserted directly into it.
|
||||
*
|
||||
* @param {Object} editorState - The current state of the editor. It provides necessary details like selection, schema, etc.
|
||||
* @param {string} fileUrl - The URL of the image to be inserted into the editor.
|
||||
* @returns {Object|null} An object containing details about the node to be inserted and its position. It returns null if no image node can be created.
|
||||
* @property {Node} node - The ProseMirror node to be inserted (either an image node or a paragraph containing the image).
|
||||
* @property {number} pos - The position where the new node should be inserted in the editor.
|
||||
*/
|
||||
|
||||
export const findNodeToInsertImage = (editorState, fileUrl) => {
|
||||
const { selection, schema } = editorState;
|
||||
const { nodes } = schema;
|
||||
const currentNode = selection.$from.node();
|
||||
const {
|
||||
type: { name: typeName },
|
||||
content: { size, content },
|
||||
} = currentNode;
|
||||
|
||||
const imageNode = nodes.image.create({ src: fileUrl });
|
||||
|
||||
if (!imageNode) return null;
|
||||
|
||||
const isInParagraph = typeName === 'paragraph';
|
||||
const needsNewLine =
|
||||
!content.some(n => n.type.name === 'image') && size !== 0 ? 1 : 0;
|
||||
|
||||
return {
|
||||
node: isInParagraph ? imageNode : nodes.paragraph.create({}, imageNode),
|
||||
pos: selection.from + needsNewLine,
|
||||
};
|
||||
};
|
||||
@@ -49,38 +49,3 @@ export const validateLoggedInRoutes = (to, user, roleWiseRoutes) => {
|
||||
// Proceed to the route if none of the above conditions are met
|
||||
return null;
|
||||
};
|
||||
|
||||
export const isAConversationRoute = routeName =>
|
||||
[
|
||||
'inbox_conversation',
|
||||
'conversation_through_mentions',
|
||||
'conversation_through_unattended',
|
||||
'conversation_through_inbox',
|
||||
'conversations_through_label',
|
||||
'conversations_through_team',
|
||||
'conversations_through_folders',
|
||||
'conversation_through_participating',
|
||||
].includes(routeName);
|
||||
|
||||
export const getConversationDashboardRoute = routeName => {
|
||||
switch (routeName) {
|
||||
case 'inbox_conversation':
|
||||
return 'home';
|
||||
case 'conversation_through_mentions':
|
||||
return 'conversation_mentions';
|
||||
case 'conversation_through_unattended':
|
||||
return 'conversation_unattended';
|
||||
case 'conversations_through_label':
|
||||
return 'label_conversations';
|
||||
case 'conversations_through_team':
|
||||
return 'team_conversations';
|
||||
case 'conversations_through_folders':
|
||||
return 'folder_conversations';
|
||||
case 'conversation_through_participating':
|
||||
return 'conversation_participating';
|
||||
case 'conversation_through_inbox':
|
||||
return 'inbox_dashboard';
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
import {
|
||||
findSignatureInBody,
|
||||
appendSignature,
|
||||
removeSignature,
|
||||
replaceSignature,
|
||||
cleanSignature,
|
||||
extractTextFromMarkdown,
|
||||
} from '../editorHelper';
|
||||
|
||||
const NEW_SIGNATURE = 'This is a new signature';
|
||||
|
||||
const DOES_NOT_HAVE_SIGNATURE = {
|
||||
'no signature': {
|
||||
body: 'This is a test',
|
||||
signature: 'This is a signature',
|
||||
},
|
||||
'text after signature': {
|
||||
body: 'This is a test\n\n--\n\nThis is a signature\n\nThis is more text',
|
||||
signature: 'This is a signature',
|
||||
},
|
||||
'signature has images': {
|
||||
body: 'This is a test',
|
||||
signature:
|
||||
'Testing \n',
|
||||
},
|
||||
'signature has non commonmark syntax': {
|
||||
body: 'This is a test',
|
||||
signature: '- Signature',
|
||||
},
|
||||
'signature has trailing spaces': {
|
||||
body: 'This is a test',
|
||||
signature: '**hello** \n**world**',
|
||||
},
|
||||
};
|
||||
|
||||
const HAS_SIGNATURE = {
|
||||
'signature at end': {
|
||||
body: 'This is a test\n\n--\n\nThis is a signature',
|
||||
signature: 'This is a signature',
|
||||
},
|
||||
'signature at end with spaces and new lines': {
|
||||
body: 'This is a test\n\n--\n\nThis is a signature \n\n',
|
||||
signature: 'This is a signature ',
|
||||
},
|
||||
'no text before signature': {
|
||||
body: '\n\n--\n\nThis is a signature',
|
||||
signature: 'This is a signature',
|
||||
},
|
||||
'signature has non-commonmark syntax': {
|
||||
body: '\n\n--\n\n* Signature',
|
||||
signature: '- Signature',
|
||||
},
|
||||
};
|
||||
|
||||
describe('findSignatureInBody', () => {
|
||||
it('returns -1 if there is no signature', () => {
|
||||
Object.keys(DOES_NOT_HAVE_SIGNATURE).forEach(key => {
|
||||
const { body, signature } = DOES_NOT_HAVE_SIGNATURE[key];
|
||||
expect(findSignatureInBody(body, signature)).toBe(-1);
|
||||
});
|
||||
});
|
||||
it('returns the index of the signature if there is one', () => {
|
||||
Object.keys(HAS_SIGNATURE).forEach(key => {
|
||||
const { body, signature } = HAS_SIGNATURE[key];
|
||||
expect(findSignatureInBody(body, signature)).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('appendSignature', () => {
|
||||
it('appends the signature if it is not present', () => {
|
||||
Object.keys(DOES_NOT_HAVE_SIGNATURE).forEach(key => {
|
||||
const { body, signature } = DOES_NOT_HAVE_SIGNATURE[key];
|
||||
const cleanedSignature = cleanSignature(signature);
|
||||
expect(
|
||||
appendSignature(body, signature).includes(cleanedSignature)
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
it('does not append signature if already present', () => {
|
||||
Object.keys(HAS_SIGNATURE).forEach(key => {
|
||||
const { body, signature } = HAS_SIGNATURE[key];
|
||||
expect(appendSignature(body, signature)).toBe(body);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeSignature', () => {
|
||||
it('does not remove signature if not present', () => {
|
||||
Object.keys(DOES_NOT_HAVE_SIGNATURE).forEach(key => {
|
||||
const { body, signature } = DOES_NOT_HAVE_SIGNATURE[key];
|
||||
expect(removeSignature(body, signature)).toBe(body);
|
||||
});
|
||||
});
|
||||
it('removes signature if present at the end', () => {
|
||||
const { body, signature } = HAS_SIGNATURE['signature at end'];
|
||||
expect(removeSignature(body, signature)).toBe('This is a test\n\n');
|
||||
});
|
||||
it('removes signature if present with spaces and new lines', () => {
|
||||
const { body, signature } = HAS_SIGNATURE[
|
||||
'signature at end with spaces and new lines'
|
||||
];
|
||||
expect(removeSignature(body, signature)).toBe('This is a test\n\n');
|
||||
});
|
||||
it('removes signature if present without text before it', () => {
|
||||
const { body, signature } = HAS_SIGNATURE['no text before signature'];
|
||||
expect(removeSignature(body, signature)).toBe('\n\n');
|
||||
});
|
||||
it('removes just the delimiter if no signature is present', () => {
|
||||
expect(removeSignature('This is a test\n\n--', 'This is a signature')).toBe(
|
||||
'This is a test\n\n'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('replaceSignature', () => {
|
||||
it('appends the new signature if not present', () => {
|
||||
Object.keys(DOES_NOT_HAVE_SIGNATURE).forEach(key => {
|
||||
const { body, signature } = DOES_NOT_HAVE_SIGNATURE[key];
|
||||
expect(replaceSignature(body, signature, NEW_SIGNATURE)).toBe(
|
||||
`${body}\n\n--\n\n${NEW_SIGNATURE}`
|
||||
);
|
||||
});
|
||||
});
|
||||
it('removes signature if present at the end', () => {
|
||||
const { body, signature } = HAS_SIGNATURE['signature at end'];
|
||||
expect(replaceSignature(body, signature, NEW_SIGNATURE)).toBe(
|
||||
`This is a test\n\n--\n\n${NEW_SIGNATURE}`
|
||||
);
|
||||
});
|
||||
it('removes signature if present with spaces and new lines', () => {
|
||||
const { body, signature } = HAS_SIGNATURE[
|
||||
'signature at end with spaces and new lines'
|
||||
];
|
||||
expect(replaceSignature(body, signature, NEW_SIGNATURE)).toBe(
|
||||
`This is a test\n\n--\n\n${NEW_SIGNATURE}`
|
||||
);
|
||||
});
|
||||
it('removes signature if present without text before it', () => {
|
||||
const { body, signature } = HAS_SIGNATURE['no text before signature'];
|
||||
expect(replaceSignature(body, signature, NEW_SIGNATURE)).toBe(
|
||||
`\n\n--\n\n${NEW_SIGNATURE}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractTextFromMarkdown', () => {
|
||||
it('should extract text from markdown and remove all images, code blocks, links, headers, bold, italic, lists etc.', () => {
|
||||
const markdown = `
|
||||
# Hello World
|
||||
|
||||
This is a **bold** text with a [link](https://example.com).
|
||||
|
||||
\`\`\`javascript
|
||||
const foo = 'bar';
|
||||
console.log(foo);
|
||||
\`\`\`
|
||||
|
||||
Here's an image: 
|
||||
|
||||
- List item 1
|
||||
- List item 2
|
||||
|
||||
*Italic text*
|
||||
`;
|
||||
|
||||
const expected =
|
||||
"Hello World\nThis is a bold text with a link.\nHere's an image:\nList item 1\nList item 2\nItalic text";
|
||||
expect(extractTextFromMarkdown(markdown)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
@@ -1,100 +0,0 @@
|
||||
import { findNodeToInsertImage } from '../messageEditorHelper';
|
||||
|
||||
describe('findNodeToInsertImage', () => {
|
||||
let mockEditorState;
|
||||
|
||||
beforeEach(() => {
|
||||
mockEditorState = {
|
||||
selection: {
|
||||
$from: {
|
||||
node: jest.fn(() => ({})),
|
||||
},
|
||||
from: 0,
|
||||
},
|
||||
schema: {
|
||||
nodes: {
|
||||
image: {
|
||||
create: jest.fn(attrs => ({ type: { name: 'image' }, attrs })),
|
||||
},
|
||||
paragraph: {
|
||||
create: jest.fn((_, node) => ({
|
||||
type: { name: 'paragraph' },
|
||||
content: [node],
|
||||
})),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
it('should insert image directly into an empty paragraph', () => {
|
||||
const mockNode = {
|
||||
type: { name: 'paragraph' },
|
||||
content: { size: 0, content: [] },
|
||||
};
|
||||
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
||||
|
||||
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
||||
expect(result).toEqual({
|
||||
node: { type: { name: 'image' }, attrs: { src: 'image-url' } },
|
||||
pos: 0,
|
||||
});
|
||||
});
|
||||
|
||||
it('should insert image directly into a paragraph without an image but with other content', () => {
|
||||
const mockNode = {
|
||||
type: { name: 'paragraph' },
|
||||
content: {
|
||||
size: 1,
|
||||
content: [
|
||||
{
|
||||
type: { name: 'text' },
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
||||
mockEditorState.selection.from = 1;
|
||||
|
||||
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
||||
expect(result).toEqual({
|
||||
node: { type: { name: 'image' }, attrs: { src: 'image-url' } },
|
||||
pos: 2, // Because it should insert after the text, on a new line.
|
||||
});
|
||||
});
|
||||
|
||||
it("should wrap image in a new paragraph when the current node isn't a paragraph", () => {
|
||||
const mockNode = {
|
||||
type: { name: 'not-a-paragraph' },
|
||||
content: { size: 0, content: [] },
|
||||
};
|
||||
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
||||
|
||||
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
||||
expect(result.node.type.name).toBe('paragraph');
|
||||
expect(result.node.content[0].type.name).toBe('image');
|
||||
expect(result.node.content[0].attrs.src).toBe('image-url');
|
||||
expect(result.pos).toBe(0);
|
||||
});
|
||||
|
||||
it('should insert a new image directly into the paragraph that already contains an image', () => {
|
||||
const mockNode = {
|
||||
type: { name: 'paragraph' },
|
||||
content: {
|
||||
size: 1,
|
||||
content: [
|
||||
{
|
||||
type: { name: 'image', attrs: { src: 'existing-image-url' } },
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
||||
mockEditorState.selection.from = 1;
|
||||
|
||||
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
||||
expect(result.node.type.name).toBe('image');
|
||||
expect(result.node.attrs.src).toBe('image-url');
|
||||
expect(result.pos).toBe(1);
|
||||
});
|
||||
});
|
||||
@@ -1,8 +1,6 @@
|
||||
import {
|
||||
getConversationDashboardRoute,
|
||||
getCurrentAccount,
|
||||
getUserRole,
|
||||
isAConversationRoute,
|
||||
routeIsAccessibleFor,
|
||||
validateLoggedInRoutes,
|
||||
} from '../routeHelpers';
|
||||
@@ -96,41 +94,3 @@ describe('#validateLoggedInRoutes', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('isAConversationRoute', () => {
|
||||
it('returns true if conversation route name is provided', () => {
|
||||
expect(isAConversationRoute('inbox_conversation')).toBe(true);
|
||||
expect(isAConversationRoute('conversation_through_inbox')).toBe(true);
|
||||
expect(isAConversationRoute('conversations_through_label')).toBe(true);
|
||||
expect(isAConversationRoute('conversations_through_team')).toBe(true);
|
||||
expect(isAConversationRoute('dashboard')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getConversationDashboardRoute', () => {
|
||||
it('returns dashboard route for conversation', () => {
|
||||
expect(getConversationDashboardRoute('inbox_conversation')).toEqual('home');
|
||||
expect(
|
||||
getConversationDashboardRoute('conversation_through_mentions')
|
||||
).toEqual('conversation_mentions');
|
||||
expect(
|
||||
getConversationDashboardRoute('conversation_through_unattended')
|
||||
).toEqual('conversation_unattended');
|
||||
expect(
|
||||
getConversationDashboardRoute('conversations_through_label')
|
||||
).toEqual('label_conversations');
|
||||
expect(getConversationDashboardRoute('conversations_through_team')).toEqual(
|
||||
'team_conversations'
|
||||
);
|
||||
expect(
|
||||
getConversationDashboardRoute('conversations_through_folders')
|
||||
).toEqual('folder_conversations');
|
||||
expect(
|
||||
getConversationDashboardRoute('conversation_through_participating')
|
||||
).toEqual('conversation_participating');
|
||||
expect(getConversationDashboardRoute('conversation_through_inbox')).toEqual(
|
||||
'inbox_dashboard'
|
||||
);
|
||||
expect(getConversationDashboardRoute('non_existent_route')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,53 +0,0 @@
|
||||
import { uploadFile } from '../uploadHelper';
|
||||
import axios from 'axios';
|
||||
|
||||
// Mocking axios using jest-mock-axios
|
||||
global.axios = axios;
|
||||
jest.mock('axios');
|
||||
|
||||
describe('#Upload Helpers', () => {
|
||||
afterEach(() => {
|
||||
// Cleaning up the mock after each test
|
||||
axios.post.mockReset();
|
||||
});
|
||||
|
||||
it('should send a POST request with correct data', async () => {
|
||||
const mockFile = new File(['dummy content'], 'example.png', {
|
||||
type: 'image/png',
|
||||
});
|
||||
const mockResponse = {
|
||||
data: {
|
||||
file_url: 'https://example.com/fileUrl',
|
||||
blob_key: 'blobKey123',
|
||||
blob_id: 'blobId456',
|
||||
},
|
||||
};
|
||||
|
||||
axios.post.mockResolvedValueOnce(mockResponse);
|
||||
|
||||
const result = await uploadFile(mockFile, '1602');
|
||||
|
||||
expect(axios.post).toHaveBeenCalledWith(
|
||||
'/api/v1/accounts/1602/upload',
|
||||
expect.any(FormData),
|
||||
{ headers: { 'Content-Type': 'multipart/form-data' } }
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
fileUrl: 'https://example.com/fileUrl',
|
||||
blobKey: 'blobKey123',
|
||||
blobId: 'blobId456',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle errors', async () => {
|
||||
const mockFile = new File(['dummy content'], 'example.png', {
|
||||
type: 'image/png',
|
||||
});
|
||||
const mockError = new Error('Failed to upload');
|
||||
|
||||
axios.post.mockRejectedValueOnce(mockError);
|
||||
|
||||
await expect(uploadFile(mockFile)).rejects.toThrow('Failed to upload');
|
||||
});
|
||||
});
|
||||
@@ -1,49 +0,0 @@
|
||||
/* global axios */
|
||||
|
||||
/**
|
||||
* Constants and Configuration
|
||||
*/
|
||||
|
||||
// Version for the API endpoint.
|
||||
const API_VERSION = 'v1';
|
||||
|
||||
// Default headers to be used in the axios request.
|
||||
const HEADERS = {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
};
|
||||
|
||||
/**
|
||||
* Uploads a file to the server.
|
||||
*
|
||||
* This function sends a POST request to a given API endpoint and uploads the specified file.
|
||||
* The function uses FormData to wrap the file and axios to send the request.
|
||||
*
|
||||
* @param {File} file - The file to be uploaded. It should be a File object (typically coming from a file input element).
|
||||
* @returns {Promise} A promise that resolves with the server's response when the upload is successful, or rejects if there's an error.
|
||||
*/
|
||||
export async function uploadFile(file, accountId) {
|
||||
// Create a new FormData instance.
|
||||
let formData = new FormData();
|
||||
|
||||
if (!accountId) {
|
||||
accountId = window.location.pathname.split('/')[3];
|
||||
}
|
||||
|
||||
// Append the file to the FormData instance under the key 'attachment'.
|
||||
formData.append('attachment', file);
|
||||
|
||||
// Use axios to send a POST request to the upload endpoint.
|
||||
const { data } = await axios.post(
|
||||
`/api/${API_VERSION}/accounts/${accountId}/upload`,
|
||||
formData,
|
||||
{
|
||||
headers: HEADERS,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
fileUrl: data.file_url,
|
||||
blobKey: data.blob_key,
|
||||
blobId: data.blob_id,
|
||||
};
|
||||
}
|
||||
@@ -26,7 +26,6 @@ import pt_BR from './locale/pt_BR';
|
||||
import ro from './locale/ro';
|
||||
import ru from './locale/ru';
|
||||
import sk from './locale/sk';
|
||||
import sr from './locale/sr';
|
||||
import sv from './locale/sv';
|
||||
import ta from './locale/ta';
|
||||
import th from './locale/th';
|
||||
@@ -67,7 +66,6 @@ export default {
|
||||
ro,
|
||||
ru,
|
||||
sk,
|
||||
sr,
|
||||
sv,
|
||||
ta,
|
||||
th,
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "إنشاء توقيع رسالة شخصية يتم إضافتها إلى جميع الرسائل التي ترسلها من المنصة. استخدم محرر المحتوى الغني لإنشاء توقيع شديد التخصيص.",
|
||||
"BTN_TEXT": "حفظ توقيع الرسالة",
|
||||
"API_ERROR": "تعذر إرسال الرسالة! حاول مرة أخرى",
|
||||
"API_SUCCESS": "تم حفظ التوقيع بنجاح",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "تم حفظ التوقيع بنجاح"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "توقيع الرسالة",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Opret en personlig besked signatur, der vil blive føjet til alle de meddelelser, du sender fra din e-mail indbakke. Brug den rige content editor til at oprette en meget personlig signatur.",
|
||||
"BTN_TEXT": "Gem beskedsignatur",
|
||||
"API_ERROR": "Kunne ikke gemme signatur! Prøv igen",
|
||||
"API_SUCCESS": "Signatur gemt",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Signatur gemt"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Besked Signatur",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Erstellen Sie eine persönliche Nachrichtensignatur, die allen Nachrichten hinzugefügt wird, die Sie aus Ihrem E-Mail-Posteingang senden. Verwenden Sie den Rich-Content-Editor, um eine stark personalisierte Signatur zu erstellen.",
|
||||
"BTN_TEXT": "Nachrichten-Signatur speichern",
|
||||
"API_ERROR": "Signatur konnte nicht gespeichert werden! Versuch es noch einmal",
|
||||
"API_SUCCESS": "Signatur erfolgreich gespeichert",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Bildgröße sollte kleiner als {size}MB sein"
|
||||
"API_SUCCESS": "Signatur erfolgreich gespeichert"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Nachrichten-Signatur",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Δημιουργήστε μια προσωπική υπογραφή, η οποία θα προστεθεί σε όλα τα μηνύματα που στέλνετε από τα εισερχόμενα email σας. Χρησιμοποιήστε τον επεξεργαστή πλούσιου περιεχομένου για να δημιουργήσετε μια εξατομικευμένη υπογραφή.",
|
||||
"BTN_TEXT": "Αποθήκευση υπογραφής μηνύματος",
|
||||
"API_ERROR": "Δεν ήταν δυνατή η αποθήκευση της υπογραφής! Δοκιμάστε ξανά",
|
||||
"API_SUCCESS": "Η υπογραφή αποθηκεύτηκε με επιτυχία",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Η υπογραφή αποθηκεύτηκε με επιτυχία"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Υπογραφή μηνύματος",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -57,15 +57,15 @@
|
||||
"DELETE": "%{agentName} eliminó una macro (#%{id})"
|
||||
},
|
||||
"INBOX_MEMBER": {
|
||||
"ADD": "%{agentName} agregó %{user} a la bandeja de entrada (#%{inbox_id})",
|
||||
"REMOVE": "%{agentName} eliminó %{user} de la bandeja de entrada(#%{inbox_id})"
|
||||
"ADD": "%{agentName} added %{user} to the inbox(#%{inbox_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the inbox(#%{inbox_id})"
|
||||
},
|
||||
"TEAM_MEMBER": {
|
||||
"ADD": "%{agentName} agregó %{user} al equipo (#%{team_id})",
|
||||
"REMOVE": "%{agentName} eliminó %{user} del equipo (#%{team_id})"
|
||||
"ADD": "%{agentName} added %{user} to the team(#%{team_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the team(#%{team_id})"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "%{agentName} Actualizó la configuración de la cuenta (#%{id})"
|
||||
"EDIT": "%{agentName} updated the account configuration (#%{id})"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
},
|
||||
"TAB_HEADING": "Conversaciones",
|
||||
"MENTION_HEADING": "Menciones",
|
||||
"UNATTENDED_HEADING": "Desatendido",
|
||||
"UNATTENDED_HEADING": "Sin atender",
|
||||
"SEARCH": {
|
||||
"INPUT": "Búsqueda de Personas, Chats, Respuestas Salvadas .."
|
||||
},
|
||||
@@ -20,7 +20,7 @@
|
||||
},
|
||||
"CHAT_STATUS_FILTER_ITEMS": {
|
||||
"open": {
|
||||
"TEXT": "Abrir"
|
||||
"TEXT": "Abiertas"
|
||||
},
|
||||
"resolved": {
|
||||
"TEXT": "Resueltas"
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"SUBMIT": "Importar",
|
||||
"CANCEL": "Cancelar"
|
||||
},
|
||||
"SUCCESS_MESSAGE": "Se le notificará por correo electrónico cuando se complete la importación.",
|
||||
"SUCCESS_MESSAGE": "You will be notified via email when the import is complete.",
|
||||
"ERROR_MESSAGE": "Hubo un error, por favor inténtelo de nuevo"
|
||||
},
|
||||
"EXPORT_CONTACTS": {
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
"NO_INBOX_AGENT": "¡Uh Oh! Parece que no eres parte de ninguna bandeja de entrada. Por favor, contacta con tu administrador",
|
||||
"SEARCH_MESSAGES": "Buscar mensajes en conversaciones",
|
||||
"EMPTY_STATE": {
|
||||
"CMD_BAR": "para abrir el menú de comandos",
|
||||
"KEYBOARD_SHORTCUTS": "para ver los atajos del teclado"
|
||||
"CMD_BAR": "to open command menu",
|
||||
"KEYBOARD_SHORTCUTS": "to view keyboard shortcuts"
|
||||
},
|
||||
"SEARCH": {
|
||||
"TITLE": "Buscar mensajes",
|
||||
|
||||
@@ -134,7 +134,7 @@
|
||||
"GO_TO_NOTIFICATIONS": "Ir a Notificaciones",
|
||||
"ADD_LABELS_TO_CONVERSATION": "Añadir etiqueta a la conversación",
|
||||
"ASSIGN_AN_AGENT": "Asignar un agente",
|
||||
"AI_ASSIST": "Asistencia AI",
|
||||
"AI_ASSIST": "AI Assist",
|
||||
"ASSIGN_PRIORITY": "Asignar prioridad",
|
||||
"ASSIGN_A_TEAM": "Asignar un equipo",
|
||||
"MUTE_CONVERSATION": "Silenciar conversación",
|
||||
@@ -160,7 +160,7 @@
|
||||
"LOADING_MESSAGE": "Cargando aplicación del tablero..."
|
||||
},
|
||||
"COMMON": {
|
||||
"OR": "O",
|
||||
"OR": "Or",
|
||||
"CLICK_HERE": "haz clic aquí"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -112,14 +112,14 @@
|
||||
"ERROR": "Este campo es obligatorio"
|
||||
},
|
||||
"API_KEY": {
|
||||
"USE_API_KEY": "Usar autenticación de Clave API",
|
||||
"USE_API_KEY": "Use API Key Authentication",
|
||||
"LABEL": "API Key SID",
|
||||
"PLACEHOLDER": "Por favor ingrese su API Key SID",
|
||||
"PLACEHOLDER": "Please enter your API Key SID",
|
||||
"ERROR": "Este campo es obligatorio"
|
||||
},
|
||||
"API_KEY_SECRET": {
|
||||
"LABEL": "Clave API secreta",
|
||||
"PLACEHOLDER": "Por favor ingrese su Clave API secreta",
|
||||
"LABEL": "API Key Secret",
|
||||
"PLACEHOLDER": "Please enter your API Key Secret",
|
||||
"ERROR": "Este campo es obligatorio"
|
||||
},
|
||||
"MESSAGING_SERVICE_SID": {
|
||||
@@ -403,21 +403,21 @@
|
||||
"DISABLED": "Deshabilitado"
|
||||
},
|
||||
"SENDER_NAME_SECTION": {
|
||||
"TITLE": "Nombre del remitente",
|
||||
"SUB_TEXT": "Seleccione el nombre que se muestra al cliente cuando reciba correos electrónicos de sus agentes.",
|
||||
"FOR_EG": "Por ejemplo:",
|
||||
"TITLE": "Sender name",
|
||||
"SUB_TEXT": "Select the name shown to the your customer when they receive emails from your agents.",
|
||||
"FOR_EG": "For eg:",
|
||||
"FRIENDLY": {
|
||||
"TITLE": "Amigable",
|
||||
"FROM": "De",
|
||||
"SUBTITLE": "Añada el nombre del agente que envió la respuesta en el nombre del remitente para que sea amistoso."
|
||||
"SUBTITLE": "Add the name of the agent who sent the reply in the sender name to make it friendly."
|
||||
},
|
||||
"PROFESSIONAL": {
|
||||
"TITLE": "Profesional",
|
||||
"SUBTITLE": "Utilice sólo el nombre del negocio configurado como nombre del remitente en el encabezado del correo electrónico."
|
||||
"SUBTITLE": "Use only the configured business name as the sender name in the email header."
|
||||
},
|
||||
"BUSINESS_NAME": {
|
||||
"BUTTON_TEXT": "+ Configura el nombre de tu negocio",
|
||||
"PLACEHOLDER": "Introduce el nombre de tu negocio",
|
||||
"BUTTON_TEXT": "+ Configure your business name",
|
||||
"PLACEHOLDER": "Enter your business name",
|
||||
"SAVE_BUTTON_TEXT": "Guardar"
|
||||
}
|
||||
},
|
||||
@@ -484,9 +484,9 @@
|
||||
"ENABLE_EMAIL_COLLECT_BOX_SUB_TEXT": "Activar o desactivar la caja de recolección de correo electrónico",
|
||||
"AUTO_ASSIGNMENT": "Activar asignación automática",
|
||||
"ENABLE_CSAT": "Habilitar Encuesta de Satisfacción",
|
||||
"SENDER_NAME_SECTION": "Habilitar nombre del agente en el correo electrónico",
|
||||
"SENDER_NAME_SECTION": "Enable Agent Name in Email",
|
||||
"ENABLE_CSAT_SUB_TEXT": "Habilitar/deshabilitar encuesta CSAT(satisfacción del cliente) después de resolver una conversación",
|
||||
"SENDER_NAME_SECTION_TEXT": "Habilitar/Deshabilitar mostrando el nombre del agente en el correo electrónico, si está deshabilitado, mostrará el nombre del negocio",
|
||||
"SENDER_NAME_SECTION_TEXT": "Enable/Disable showing Agent's name in email, if disabled it will show business name",
|
||||
"ENABLE_CONTINUITY_VIA_EMAIL": "Habilitar continuidad de conversación por correo electrónico",
|
||||
"ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT": "Las conversaciones continuarán por correo electrónico si la dirección de correo electrónico de contacto está disponible.",
|
||||
"LOCK_TO_SINGLE_CONVERSATION": "Bloquear a una sola conversación",
|
||||
|
||||
@@ -75,25 +75,25 @@
|
||||
"SLACK": {
|
||||
"DELETE": "Eliminar",
|
||||
"DELETE_CONFIRMATION": {
|
||||
"TITLE": "Eliminar la integración",
|
||||
"MESSAGE": "¿Está seguro que desea eliminar la integración? Si lo hace, perderá el acceso a las conversaciones en su espacio de trabajo Slack."
|
||||
"TITLE": "Delete the integration",
|
||||
"MESSAGE": "Are you sure you want to delete the integration? Doing so will result in the loss of access to conversations on your Slack workspace."
|
||||
},
|
||||
"HELP_TEXT": {
|
||||
"TITLE": "¿Cómo utilizar la Integración Slack?",
|
||||
"TITLE": "Utilizando integración de Slack",
|
||||
"BODY": "<br/><p>Chatwoot ahora sincronizará todas las conversaciones entrantes en el canal <b><i>de conversaciones del cliente</i></b> dentro de tu lugar de trabajo slack.</p><p>Respondiendo a un tema de conversación en <b><i>conversaciones de clientes</i></b> canal de slack creará una respuesta al cliente a través de chatwoot.</p><p>Inicie las respuestas con <b><i>nota:</i></b> para crear notas privadas en lugar de respuestas.</p><p>Si el respondente de slack tiene un perfil de agente en el chatwoot bajo el mismo correo electrónico, las respuestas se asociarán en consecuencia.</p><p>Cuando el replicador no tiene un perfil de agente asociado, las respuestas se harán con el perfil del bot.</p>",
|
||||
"SELECTED": "seleccionado"
|
||||
"SELECTED": "selected"
|
||||
},
|
||||
"SELECT_CHANNEL": {
|
||||
"OPTION_LABEL": "Seleccione un canal",
|
||||
"OPTION_LABEL": "Select a channel",
|
||||
"UPDATE": "Actualizar",
|
||||
"BUTTON_TEXT": "Conectar canal",
|
||||
"DESCRIPTION": "Su espacio de trabajo de Slack ahora esta enlazado con Chatwoot. Sin embarbo, la integración esta actualmente inactiva. Para activar la integración y conectar un canal a Chatwoot, por favor haga clic en el siguiente boton.\n\n**Nota.** Si esta intentando conectar un canal privado, agregue la aplicación Chatwoot al canal de Slack antes de proceder con este paso.",
|
||||
"ATTENTION_REQUIRED": "Atención requerida",
|
||||
"EXPIRED": "Su integración de Slack ha caducado. Para continuar recibiendo mensajes en Slack, por favor elimine la integración y vuelva a conectar su área de trabajo."
|
||||
"BUTTON_TEXT": "Connect channel",
|
||||
"DESCRIPTION": "Your Slack workspace is now linked with Chatwoot. However, the integration is currently inactive. To activate the integration and connect a channel to Chatwoot, please click the button below.\n\n**Note:** If you are attempting to connect a private channel, add the Chatwoot app to the Slack channel before proceeding with this step.",
|
||||
"ATTENTION_REQUIRED": "Attention required",
|
||||
"EXPIRED": "Your Slack integration has expired. To continue receiving messages on Slack, please delete the integration and connect your workspace again."
|
||||
},
|
||||
"UPDATE_ERROR": "Se presento un error actualizando la integración, por favor inténtelo nuevamente",
|
||||
"UPDATE_SUCCESS": "El canal está conectado correctamente",
|
||||
"FAILED_TO_FETCH_CHANNELS": "Hubo un error obteniendo los canales de Slack, por favor inténtalo de nuevo"
|
||||
"UPDATE_ERROR": "There was an error updating the integration, please try again",
|
||||
"UPDATE_SUCCESS": "The channel is connected successfully",
|
||||
"FAILED_TO_FETCH_CHANNELS": "There was an error fetching the channels from Slack, please try again"
|
||||
},
|
||||
"DYTE": {
|
||||
"CLICK_HERE_TO_JOIN": "Haga clic aquí para unirse",
|
||||
@@ -103,39 +103,39 @@
|
||||
"CREATE_ERROR": "Hubo un error al crear un enlace de reunión, por favor inténtelo de nuevo"
|
||||
},
|
||||
"OPEN_AI": {
|
||||
"AI_ASSIST": "Asistencia AI",
|
||||
"WITH_AI": " %{option} con IA ",
|
||||
"AI_ASSIST": "AI Assist",
|
||||
"WITH_AI": " %{option} with AI ",
|
||||
"OPTIONS": {
|
||||
"REPLY_SUGGESTION": "Responder sugerencia",
|
||||
"SUMMARIZE": "Resumir",
|
||||
"REPHRASE": "Mejorar escritura",
|
||||
"FIX_SPELLING_GRAMMAR": "Corregir ortografía y gramática",
|
||||
"SHORTEN": "Acortar",
|
||||
"EXPAND": "Expandir",
|
||||
"MAKE_FRIENDLY": "Cambiar tono de mensaje a amigable",
|
||||
"MAKE_FORMAL": "Usar tono formal",
|
||||
"SIMPLIFY": "Simplificar"
|
||||
"REPLY_SUGGESTION": "Reply Suggestion",
|
||||
"SUMMARIZE": "Summarize",
|
||||
"REPHRASE": "Improve Writing",
|
||||
"FIX_SPELLING_GRAMMAR": "Fix Spelling and Grammar",
|
||||
"SHORTEN": "Shorten",
|
||||
"EXPAND": "Expand",
|
||||
"MAKE_FRIENDLY": "Change message tone to friendly",
|
||||
"MAKE_FORMAL": "Use formal tone",
|
||||
"SIMPLIFY": "Simplify"
|
||||
},
|
||||
"ASSISTANCE_MODAL": {
|
||||
"DRAFT_TITLE": "Contenido de borrador",
|
||||
"GENERATED_TITLE": "Contenido generado",
|
||||
"AI_WRITING": "AI está escribiendo",
|
||||
"DRAFT_TITLE": "Draft content",
|
||||
"GENERATED_TITLE": "Generated content",
|
||||
"AI_WRITING": "AI is writing",
|
||||
"BUTTONS": {
|
||||
"APPLY": "Usar esta sugerencia",
|
||||
"APPLY": "Use this suggestion",
|
||||
"CANCEL": "Cancelar"
|
||||
}
|
||||
},
|
||||
"CTA_MODAL": {
|
||||
"TITLE": "Integrar con OpenAI",
|
||||
"DESC": "Trae las características avanzadas de IA a tu panel de control con los modelos GPT de OpenAI. Para empezar, introduce la clave API desde tu cuenta OpenAI.",
|
||||
"KEY_PLACEHOLDER": "Introduzca su clave API OpenAI",
|
||||
"TITLE": "Integrate with OpenAI",
|
||||
"DESC": "Bring advanced AI features to your dashboard with OpenAI's GPT models. To begin, enter the API key from your OpenAI account.",
|
||||
"KEY_PLACEHOLDER": "Enter your OpenAI API key",
|
||||
"BUTTONS": {
|
||||
"NEED_HELP": "¿Necesitas ayuda?",
|
||||
"DISMISS": "Descartar",
|
||||
"FINISH": "Finalizar configuración"
|
||||
"FINISH": "Finish Setup"
|
||||
},
|
||||
"DISMISS_MESSAGE": "Puedes configurar la integración OpenAI más tarde cuando quieras.",
|
||||
"SUCCESS_MESSAGE": "Configuración de integración OpenAI exitosa"
|
||||
"DISMISS_MESSAGE": "You can setup OpenAI integration later Whenever you want.",
|
||||
"SUCCESS_MESSAGE": "OpenAI integration setup successfully"
|
||||
},
|
||||
"TITLE": "Mejorar con IA",
|
||||
"SUMMARY_TITLE": "Resumen con IA",
|
||||
@@ -153,7 +153,7 @@
|
||||
"GENERATING": "Generando...",
|
||||
"CANCEL": "Cancelar"
|
||||
},
|
||||
"GENERATE_ERROR": "Hubo un error al procesar el contenido, por favor verifique su clave API OpenAI e inténtelo de nuevo"
|
||||
"GENERATE_ERROR": "Hubo un error al procesar el contenido, por favor inténtelo de nuevo"
|
||||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Eliminar",
|
||||
|
||||
@@ -40,16 +40,16 @@
|
||||
},
|
||||
"SUGGESTIONS": {
|
||||
"TOOLTIP": {
|
||||
"SINGLE_SUGGESTION": "Añadir etiqueta a la conversación",
|
||||
"MULTIPLE_SUGGESTION": "Seleccionar esta etiqueta",
|
||||
"DESELECT": "Deseleccionar etiqueta",
|
||||
"DISMISS": "Descartar sugerencia"
|
||||
"SINGLE_SUGGESTION": "Add label to conversation",
|
||||
"MULTIPLE_SUGGESTION": "Select this label",
|
||||
"DESELECT": "Deselect label",
|
||||
"DISMISS": "Dismiss suggestion"
|
||||
},
|
||||
"POWERED_BY": "Chatwoot AI",
|
||||
"DISMISS": "Descartar",
|
||||
"ADD_SELECTED_LABELS": "Asignar etiquetas seleccionadas",
|
||||
"ADD_SELECTED_LABEL": "Añadir etiqueta seleccionada",
|
||||
"ADD_ALL_LABELS": "Añadir todas las etiquetas"
|
||||
"ADD_SELECTED_LABELS": "Add selected labels",
|
||||
"ADD_SELECTED_LABEL": "Add selected label",
|
||||
"ADD_ALL_LABELS": "Add all labels"
|
||||
},
|
||||
"ADD": {
|
||||
"TITLE": "Añadir etiqueta",
|
||||
|
||||
@@ -12,11 +12,11 @@
|
||||
"DESC": "( Total )"
|
||||
},
|
||||
"INCOMING_MESSAGES": {
|
||||
"NAME": "Mensajes recibidos",
|
||||
"NAME": "Mensajes entrantes",
|
||||
"DESC": "( Total )"
|
||||
},
|
||||
"OUTGOING_MESSAGES": {
|
||||
"NAME": "Mensajes enviados",
|
||||
"NAME": "Mensajes salientes",
|
||||
"DESC": "( Total )"
|
||||
},
|
||||
"FIRST_RESPONSE_TIME": {
|
||||
@@ -36,8 +36,8 @@
|
||||
"DESC": "( Total )"
|
||||
},
|
||||
"REPLY_TIME": {
|
||||
"NAME": "Tiempo de espera del cliente",
|
||||
"TOOLTIP_TEXT": "El tiempo de espera es %{metricValue} (basado en %{conversationCount} conversaciones)"
|
||||
"NAME": "Customer waiting time",
|
||||
"TOOLTIP_TEXT": "Waiting time is %{metricValue} (based on %{conversationCount} conversations)"
|
||||
}
|
||||
},
|
||||
"DATE_RANGE_OPTIONS": {
|
||||
@@ -117,6 +117,10 @@
|
||||
}
|
||||
],
|
||||
"GROUP_BY_YEAR_OPTIONS": [
|
||||
{
|
||||
"id": 1,
|
||||
"groupBy": "Día"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"groupBy": "Semana"
|
||||
@@ -124,10 +128,6 @@
|
||||
{
|
||||
"id": 3,
|
||||
"groupBy": "Mes"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"groupBy": "Mes"
|
||||
}
|
||||
],
|
||||
"BUSINESS_HOURS": "Horarios"
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"RESET_PASSWORD": {
|
||||
"TITLE": "Restablecer contraseña",
|
||||
"DESCRIPTION": "Introduzca la dirección de correo electrónico que utiliza para iniciar sesión en Chatwoot para obtener las instrucciones de restablecimiento de contraseña.",
|
||||
"GO_BACK_TO_LOGIN": "Si desea volver a la página de inicio de sesión,",
|
||||
"DESCRIPTION": "Enter the email address you use to log in to Chatwoot to get the password reset instructions.",
|
||||
"GO_BACK_TO_LOGIN": "If you want to go back to the login page,",
|
||||
"EMAIL": {
|
||||
"LABEL": "E-mail",
|
||||
"PLACEHOLDER": "Por favor ingrese su email.",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Cree una firma de mensaje personal que se añadirá a todos los mensajes que envíe desde su buzón de correo electrónico. Utilice el rico editor de contenidos para crear una firma altamente personalizada.",
|
||||
"BTN_TEXT": "Guardar firma de mensaje",
|
||||
"API_ERROR": "¡No se pudo guardar la firma! Inténtalo de nuevo",
|
||||
"API_SUCCESS": "Firma guardada correctamente",
|
||||
"IMAGE_UPLOAD_ERROR": "¡No se pudo subir la imagen! Intente nuevamente",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Imagen agregada satisfactoriamente. Por favor haga clic en salvar para guardar la firma",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "El tamaño de la imagen debe ser menor que {size}MB"
|
||||
"API_SUCCESS": "Firma guardada correctamente"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Firma del mensaje",
|
||||
@@ -208,7 +205,7 @@
|
||||
"AGENTS": "Agentes",
|
||||
"AGENT_BOTS": "Bots",
|
||||
"AUDIT_LOGS": "Auditoría de registros",
|
||||
"INBOXES": "Entradas",
|
||||
"INBOXES": "Bandejas de entrada",
|
||||
"NOTIFICATIONS": "Notificaciones",
|
||||
"CANNED_RESPONSES": "Respuestas predefinidas",
|
||||
"INTEGRATIONS": "Integraciones",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "یک امضای پیام شخصی ایجاد کنید که به همه پیامهایی که از صندوق ورودی ایمیل ارسال میکنید اضافه شود. از ویرایشگر محتوای غنی برای ایجاد یک امضای بسیار شخصی استفاده کنید.",
|
||||
"BTN_TEXT": "ذخیره امضای پیام",
|
||||
"API_ERROR": "امضا ذخیره نشد! دوباره امتحان کنید",
|
||||
"API_SUCCESS": "امضا با موفقیت ذخیره شد",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "اندازه تصویر باید کمتر از {size} مگابایت باشد"
|
||||
"API_SUCCESS": "امضا با موفقیت ذخیره شد"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "امضای پیام",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Créez une signature de message personnelle qui sera ajoutée à tous les messages que vous envoyez depuis votre boîte aux lettres électronique. Utilisez l'éditeur de contenu riche pour créer une signature personnalisée.",
|
||||
"BTN_TEXT": "Enregistrer la signature du message",
|
||||
"API_ERROR": "Impossible d'enregistrer la signature ! Réessayez",
|
||||
"API_SUCCESS": "Signature enregistrée avec succès",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "La taille de l'image doit être inférieure à {size}Mo"
|
||||
"API_SUCCESS": "Signature enregistrée avec succès"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Signature du message",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "ניתן לרשום חתימה לכל ההודעות שתרשום ללקוחות שלך.",
|
||||
"BTN_TEXT": "שמירה",
|
||||
"API_ERROR": "לא ניתן לשמור את החתימה! אנא נסה שנית",
|
||||
"API_SUCCESS": "החתימה נשמרה בהצלחה",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "גודל התמונה צריך להיות פחות מ-{size}MB"
|
||||
"API_SUCCESS": "החתימה נשמרה בהצלחה"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "חתימת הודעה",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Hozz létre egy személyes üzenetaláírást, amelyet az e-mail postafiókodból küldött összes üzenethez hozzáadhatsz. A sokszínű tartalomszerkesztő segítségével személyre szabott aláírást hozhatsz létre.",
|
||||
"BTN_TEXT": "Üzenet aláírás mentése",
|
||||
"API_ERROR": "Nem sikerült menteni az aláírást! Próbáld újra",
|
||||
"API_SUCCESS": "Aláírás sikeresen mentve",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "A kép mérete kevesebb, mint {size} MB"
|
||||
"API_SUCCESS": "Aláírás sikeresen mentve"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Üzenet aláírás",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Buat tanda tangan pesan pribadi yang akan ditambahkan ke semua pesan yang Anda kirim dari kotak masuk email Anda. Gunakan editor konten kaya untuk membuat tanda tangan yang sangat personal.",
|
||||
"BTN_TEXT": "Simpan tanda tangan pesan",
|
||||
"API_ERROR": "Tidak dapat menyimpan tanda tangan! Coba lagi",
|
||||
"API_SUCCESS": "Tanda tangan berhasil disimpan",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Ukuran gambar harus kurang dari {size}MB"
|
||||
"API_SUCCESS": "Tanda tangan berhasil disimpan"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Tanda Tangan Pesan",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Búðu til persónulega skilaboðaundirskrift sem verður bætt við öll skilaboðin sem þú sendir úr pósthólfinu þínu. Notaðu innihaldsríka ritilinn til að búa til mjög persónulega undirskrift.",
|
||||
"BTN_TEXT": "Vista undirskrift á skilaboðum",
|
||||
"API_ERROR": "Gat ekki vistað undirskrift! Reyndu aftur",
|
||||
"API_SUCCESS": "Undirskrift var vistuð",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Undirskrift var vistuð"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Undirskrift skilaboða",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Crea una firma personale del messaggio che sarà aggiunta a tutti i messaggi inviati dalla tua casella di posta elettronica. Usa l'editor di contenuti per creare una firma altamente personalizzata.",
|
||||
"BTN_TEXT": "Salva firma del messaggio",
|
||||
"API_ERROR": "Impossibile salvare la firma! Riprova",
|
||||
"API_SUCCESS": "Firma salvata con successo",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Firma salvata con successo"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Firma del messaggio",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Sukurkite asmeninį pranešimo parašą, kuris būtų pridėtas prie visų pranešimų, kuriuos siunčiate iš el. pašto dėžutės. Naudokite turtingo turinio redagavimo priemonę, kad sukurtumėte labai suasmenintą parašą.",
|
||||
"BTN_TEXT": "Išsaugoti pranešimo parašą",
|
||||
"API_ERROR": "Nepavyko išsaugoti parašo! Bandykite dar kartą",
|
||||
"API_SUCCESS": "Parašas išsaugotas sėkmingai",
|
||||
"IMAGE_UPLOAD_ERROR": "Nepavyko įkelti vaizdo! Prašau, pabandykite dar kartą",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Vaizdas sėkmingai pridėtas. Spustelėkite \"Išsaugoti\", kad išsaugotumėte parašą",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Paveiksliuko dydis turi būti mažesnis nei {size} MB"
|
||||
"API_SUCCESS": "Parašas išsaugotas sėkmingai"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Pranešimo parašas",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Izveidojiet personīgu ziņojumu parakstu, kas tiks pievienots visiem ziņojumiem, kurus nosūtat no jūsu e -pasta iesūtnes. Izmantojiet bagātīgā satura redaktoru, lai izveidotu īpaši personalizētu parakstu.",
|
||||
"BTN_TEXT": "Saglabāt ziņojuma parakstu",
|
||||
"API_ERROR": "Nevarēja saglabāt parakstu! Lūdzu, mēģiniet vēlreiz",
|
||||
"API_SUCCESS": "Paraksts ir veiksmīgi saglabāts",
|
||||
"IMAGE_UPLOAD_ERROR": "Nevarēja augšupielādēt attēlu! Lūdzu, mēģiniet vēlreiz",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Attēls ir veiksmīgi pievienots. Lūdzu, noklikšķiniet uz saglabāt, lai saglabātu parakstu",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Attēla izmēram ir jābūt mazākam par {size}MB"
|
||||
"API_SUCCESS": "Paraksts ir veiksmīgi saglabāts"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Ziņojuma Paraksts",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -39,10 +39,7 @@
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
"IMAGE_UPLOAD_ERROR": "Couldn't upload image! Try again",
|
||||
"IMAGE_UPLOAD_SUCCESS": "Image added successfully. Please click on save to save the signature",
|
||||
"IMAGE_UPLOAD_SIZE_ERROR": "Image size should be less than {size}MB"
|
||||
"API_SUCCESS": "Signature saved successfully"
|
||||
},
|
||||
"MESSAGE_SIGNATURE": {
|
||||
"LABEL": "Message Signature",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"TITLE": "Conversaties filteren",
|
||||
"SUBTITLE": "Voeg hieronder filters toe en druk op 'Filters toepassen' om te filteren.",
|
||||
"EDIT_CUSTOM_FILTER": "Map bewerken",
|
||||
"CUSTOM_VIEWS_SUBTITLE": "Filters toevoegen of verwijderen en bijwerken van uw folder.",
|
||||
"CUSTOM_VIEWS_SUBTITLE": "Add or remove filters and update your folder.",
|
||||
"ADD_NEW_FILTER": "Filter toevoegen",
|
||||
"FILTER_DELETE_ERROR": "U moet minstens één filter hebben om op te slaan",
|
||||
"SUBMIT_BUTTON_LABEL": "Filters toepassen",
|
||||
@@ -11,7 +11,7 @@
|
||||
"CANCEL_BUTTON_LABEL": "Annuleren",
|
||||
"CLEAR_BUTTON_LABEL": "Filters wissen",
|
||||
"EMPTY_VALUE_ERROR": "Waarde is vereist",
|
||||
"FOLDER_LABEL": "Folder naam",
|
||||
"FOLDER_LABEL": "Folder Name",
|
||||
"FOLDER_QUERY_LABEL": "Folder Query",
|
||||
"TOOLTIP_LABEL": "Gesprekken filteren",
|
||||
"QUERY_DROPDOWN_LABELS": {
|
||||
@@ -28,7 +28,7 @@
|
||||
"is_greater_than": "Is groter dan",
|
||||
"is_less_than": "Is lager dan",
|
||||
"days_before": "Is x dagen vóór",
|
||||
"starts_with": "Begint met"
|
||||
"starts_with": "Starts with"
|
||||
},
|
||||
"ATTRIBUTE_LABELS": {
|
||||
"TRUE": "Waar",
|
||||
@@ -43,7 +43,7 @@
|
||||
"CAMPAIGN_NAME": "Campagne Naam",
|
||||
"LABELS": "Labelen",
|
||||
"BROWSER_LANGUAGE": "Browser Taal",
|
||||
"PRIORITY": "Prioriteit",
|
||||
"PRIORITY": "Priority",
|
||||
"COUNTRY_NAME": "Land Naam",
|
||||
"REFERER_LINK": "Verwijzende link",
|
||||
"CUSTOM_ATTRIBUTE_LIST": "Lijst",
|
||||
@@ -77,7 +77,7 @@
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"EDIT_BUTTON": "Map bewerken"
|
||||
"EDIT_BUTTON": "Edit folder"
|
||||
},
|
||||
"DELETE": {
|
||||
"DELETE_BUTTON": "Filter verwijderen",
|
||||
|
||||
@@ -1,68 +1,68 @@
|
||||
{
|
||||
"AGENT_BOTS": {
|
||||
"HEADER": "Bots",
|
||||
"LOADING_EDITOR": "Editor laden...",
|
||||
"HEADER_BTN_TXT": "Botconfiguratie toevoegen",
|
||||
"SIDEBAR_TXT": "<p><b>Agent Bots</b> <p>Agent bots stelt je in staat de gesprekken te automatiseren</p>",
|
||||
"LOADING_EDITOR": "Loading Editor...",
|
||||
"HEADER_BTN_TXT": "Add Bot Configuration",
|
||||
"SIDEBAR_TXT": "<p><b>Agent Bots</b> <p>Agent bots allows you to automate the conversations</p>",
|
||||
"CSML_BOT_EDITOR": {
|
||||
"NAME": {
|
||||
"LABEL": "Bot Naam",
|
||||
"PLACEHOLDER": "Geef uw bot een naam",
|
||||
"ERROR": "Bot naam is vereist"
|
||||
"PLACEHOLDER": "Give your bot a name",
|
||||
"ERROR": "Bot name is required"
|
||||
},
|
||||
"DESCRIPTION": {
|
||||
"LABEL": "Bot Beschrijving",
|
||||
"PLACEHOLDER": "Wat doet deze bot?"
|
||||
"LABEL": "Bot Description",
|
||||
"PLACEHOLDER": "What does this bot do?"
|
||||
},
|
||||
"BOT_CONFIG": {
|
||||
"ERROR": "Vul uw CSML bot configuratie hierboven in",
|
||||
"API_ERROR": "Uw CSML-configuratie is ongeldig, los het op en probeer het opnieuw."
|
||||
"ERROR": "Please enter your CSML bot configuration above",
|
||||
"API_ERROR": "Your CSML configuration is invalid, please fix it and try again."
|
||||
},
|
||||
"SUBMIT": "Valideren en opslaan"
|
||||
"SUBMIT": "Validate and save"
|
||||
},
|
||||
"BOT_CONFIGURATION": {
|
||||
"TITLE": "Selecteer een agent bot",
|
||||
"DESC": "U kunt een agent bot instellen vanuit de lijst naar deze inbox. De bot kan het gesprek in eerste instantie afhandelen en indien nodig naar een agent overbrengen.",
|
||||
"TITLE": "Select an agent bot",
|
||||
"DESC": "You can set an agent bot from the list to this inbox. The bot can initially handle the conversation and transfer it to an agent when needed.",
|
||||
"SUBMIT": "Vernieuwen",
|
||||
"DISCONNECT": "Bot verbinding verbreken",
|
||||
"SUCCESS_MESSAGE": "De agent bot is succesvol bijgewerkt",
|
||||
"DISCONNECTED_SUCCESS_MESSAGE": "Succesvol de agent bot verbinding verbroken",
|
||||
"ERROR_MESSAGE": "Kan de agent bot niet bijwerken, probeer het later opnieuw",
|
||||
"DISCONNECTED_ERROR_MESSAGE": "Kan de agent bot niet ontkoppelen, probeer het later opnieuw",
|
||||
"SELECT_PLACEHOLDER": "Selecteer Bot"
|
||||
"DISCONNECT": "Disconnect Bot",
|
||||
"SUCCESS_MESSAGE": "Successfully updated the agent bot",
|
||||
"DISCONNECTED_SUCCESS_MESSAGE": "Successfully disconnected the agent bot",
|
||||
"ERROR_MESSAGE": "Could not update the agent bot, please try again later",
|
||||
"DISCONNECTED_ERROR_MESSAGE": "Could not disconnect the agent bot, please try again later",
|
||||
"SELECT_PLACEHOLDER": "Select Bot"
|
||||
},
|
||||
"ADD": {
|
||||
"TITLE": "Configureer nieuwe bot",
|
||||
"TITLE": "Configure new bot",
|
||||
"CANCEL_BUTTON_TEXT": "Annuleren",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Bot succesvol toegevoegd",
|
||||
"ERROR_MESSAGE": "Kan bot niet toevoegen, probeer het later opnieuw"
|
||||
"SUCCESS_MESSAGE": "Bot added successfully",
|
||||
"ERROR_MESSAGE": "Could not add bot, Please try again later"
|
||||
}
|
||||
},
|
||||
"LIST": {
|
||||
"404": "Geen bots gevonden, u kunt een bot maken door op de 'Configureer nieuwe bot' knop te klikken ↗",
|
||||
"LOADING": "Bots Ophalen...",
|
||||
"404": "No Bots found, you can create a bot by clicking the 'Configure new bot' Button ↗",
|
||||
"LOADING": "Fetching Bots...",
|
||||
"TYPE": "Bot Type"
|
||||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Verwijderen",
|
||||
"TITLE": "Bot Verwijderen",
|
||||
"TITLE": "Delete Bot",
|
||||
"SUBMIT": "Verwijderen",
|
||||
"CANCEL_BUTTON_TEXT": "Annuleren",
|
||||
"DESCRIPTION": "Weet u zeker dat u deze bot wilt verwijderen? Deze actie kan niet ongedaan worden gemaakt",
|
||||
"DESCRIPTION": "Are you sure you want to delete this bot? This action is irreversible",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Bot succesvol verwijderd",
|
||||
"ERROR_MESSAGE": "Kan bot niet verwijderen, probeer het later opnieuw"
|
||||
"SUCCESS_MESSAGE": "Bot deleted successfully",
|
||||
"ERROR_MESSAGE": "Could not able to delete bot, Please try again later"
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"BUTTON_TEXT": "Bewerken",
|
||||
"LOADING": "Bots Ophalen...",
|
||||
"TITLE": "Bot Bewerken",
|
||||
"LOADING": "Fetching Bots...",
|
||||
"TITLE": "Edit Bot",
|
||||
"CANCEL_BUTTON_TEXT": "Annuleren",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Bot succesvol bijgewerkt",
|
||||
"ERROR_MESSAGE": "Kan bot niet updaten, probeer het later opnieuw"
|
||||
"SUCCESS_MESSAGE": "Bot updated successfully",
|
||||
"ERROR_MESSAGE": "Could not update bot, Please try again later"
|
||||
}
|
||||
},
|
||||
"TYPES": {
|
||||
|
||||
@@ -76,8 +76,8 @@
|
||||
},
|
||||
"AGENT_AVAILABILITY": {
|
||||
"LABEL": "Beschikbaarheid",
|
||||
"PLACEHOLDER": "Selecteer een beschikbaarheidsstatus",
|
||||
"ERROR": "Beschikbaarheid is vereist"
|
||||
"PLACEHOLDER": "Please select an availability status",
|
||||
"ERROR": "Availability is required"
|
||||
},
|
||||
"SUBMIT": "Bewerk medewerker"
|
||||
},
|
||||
@@ -111,7 +111,7 @@
|
||||
"PLACEHOLDER": {
|
||||
"AGENT": "Zoek agenten",
|
||||
"TEAM": "Teams zoeken",
|
||||
"INPUT": "Zoeken naar agents"
|
||||
"INPUT": "Search for agents"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,82 +1,82 @@
|
||||
{
|
||||
"ATTRIBUTES_MGMT": {
|
||||
"HEADER": "Aangepaste attributen",
|
||||
"HEADER_BTN_TXT": "Aangepast attribuut toevoegen",
|
||||
"LOADING": "Aangepaste attributen ophalen",
|
||||
"SIDEBAR_TXT": "<p><b>Aangepaste Attributen</b> <p>Een aangepast attribuut volgt feiten over uw contacten/conversaties - zoals het abonnement of wanneer ze voor het eerst een item bestelden etc. <br /><br />Voor het maken van een aangepast attribuut, klik op <b>Voeg Aangepast Attribuut toe.</b> U kunt ook een bestaand Aangepast Attribuut bewerken of verwijderen door op de knop Bewerken of Verwijderen te klikken.</p>",
|
||||
"HEADER": "Custom Attributes",
|
||||
"HEADER_BTN_TXT": "Add Custom Attribute",
|
||||
"LOADING": "Fetching custom attributes",
|
||||
"SIDEBAR_TXT": "<p><b>Custom 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 Custom Attribute, just click on the <b>Add Custom Attribute.</b> You can also edit or delete an existing Custom Attribute by clicking on the Edit or Delete button.</p>",
|
||||
"ADD": {
|
||||
"TITLE": "Aangepast attribuut toevoegen",
|
||||
"TITLE": "Add Custom Attribute",
|
||||
"SUBMIT": "Aanmaken",
|
||||
"CANCEL_BUTTON_TEXT": "Annuleren",
|
||||
"FORM": {
|
||||
"NAME": {
|
||||
"LABEL": "Weergave naam",
|
||||
"PLACEHOLDER": "Voer aangepaste attribuut weergavenaam in",
|
||||
"ERROR": "Naam is vereist"
|
||||
"LABEL": "Display Name",
|
||||
"PLACEHOLDER": "Enter custom attribute display name",
|
||||
"ERROR": "Name is required"
|
||||
},
|
||||
"DESC": {
|
||||
"LABEL": "Beschrijving",
|
||||
"PLACEHOLDER": "Voer aangepaste attribuutbeschrijving in",
|
||||
"ERROR": "Omschrijving is vereist"
|
||||
"PLACEHOLDER": "Enter custom attribute description",
|
||||
"ERROR": "Description is required"
|
||||
},
|
||||
"MODEL": {
|
||||
"LABEL": "Van toepassing op",
|
||||
"PLACEHOLDER": "Selecteer er een",
|
||||
"ERROR": "Model is vereist"
|
||||
"LABEL": "Applies to",
|
||||
"PLACEHOLDER": "Please select one",
|
||||
"ERROR": "Model is required"
|
||||
},
|
||||
"TYPE": {
|
||||
"LABEL": "Type",
|
||||
"PLACEHOLDER": "Selecteer een type",
|
||||
"ERROR": "Type is vereist",
|
||||
"ERROR": "Type is required",
|
||||
"LIST": {
|
||||
"LABEL": "Toon waardes",
|
||||
"PLACEHOLDER": "Voer waarde in en druk op enter-toets",
|
||||
"ERROR": "Moet minstens één waarde bevatten"
|
||||
"LABEL": "List Values",
|
||||
"PLACEHOLDER": "Please enter value and press enter key",
|
||||
"ERROR": "Must have at least one value"
|
||||
}
|
||||
},
|
||||
"KEY": {
|
||||
"LABEL": "Sleutel",
|
||||
"PLACEHOLDER": "Voer aangepaste attribuut sleutel in",
|
||||
"ERROR": "Sleutel is vereist",
|
||||
"IN_VALID": "Ongeldige sleutel"
|
||||
"LABEL": "Key",
|
||||
"PLACEHOLDER": "Enter custom attribute key",
|
||||
"ERROR": "Key is required",
|
||||
"IN_VALID": "Invalid key"
|
||||
}
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Aangepast attribuut succesvol toegevoegd!",
|
||||
"ERROR_MESSAGE": "Aanmaken van aangepaste attribuut mislukt. Probeer het later opnieuw."
|
||||
"SUCCESS_MESSAGE": "Custom Attribute added successfully!",
|
||||
"ERROR_MESSAGE": "Could not create a Custom Attribute. Please try again later."
|
||||
}
|
||||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Verwijderen",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Aangepast attribuut succesvol verwijderd.",
|
||||
"ERROR_MESSAGE": "Kan het aangepaste attribuut niet verwijderen. Probeer het opnieuw."
|
||||
"SUCCESS_MESSAGE": "Custom Attribute deleted successfully.",
|
||||
"ERROR_MESSAGE": "Couldn't delete the custom attribute. Try again."
|
||||
},
|
||||
"CONFIRM": {
|
||||
"TITLE": "Weet u zeker dat u wilt verwijderen - %{attributeName}",
|
||||
"PLACE_HOLDER": "Voer {attributeName} in om te bevestigen",
|
||||
"MESSAGE": "Verwijderen zal het aangepaste attribuut verwijderen",
|
||||
"TITLE": "Are you sure want to delete - %{attributeName}",
|
||||
"PLACE_HOLDER": "Please type {attributeName} to confirm",
|
||||
"MESSAGE": "Deleting will remove the custom attribute",
|
||||
"YES": "Verwijderen ",
|
||||
"NO": "Annuleren"
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"TITLE": "Aangepast attribuut bewerken",
|
||||
"TITLE": "Edit Custom Attribute",
|
||||
"UPDATE_BUTTON_TEXT": "Vernieuwen",
|
||||
"TYPE": {
|
||||
"LIST": {
|
||||
"LABEL": "Toon waardes",
|
||||
"PLACEHOLDER": "Voer waarden in en druk op enter-toets"
|
||||
"LABEL": "List Values",
|
||||
"PLACEHOLDER": "Please enter values and press enter key"
|
||||
}
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Aangepast attribuut succesvol bijgewerkt",
|
||||
"ERROR_MESSAGE": "Er is een fout opgetreden bij het bijwerken van aangepaste attributen, probeer het opnieuw"
|
||||
"SUCCESS_MESSAGE": "Custom Attribute updated successfully",
|
||||
"ERROR_MESSAGE": "There was an error updating custom attribute, please try again"
|
||||
}
|
||||
},
|
||||
"TABS": {
|
||||
"HEADER": "Aangepaste attributen",
|
||||
"CONVERSATION": "Gesprek",
|
||||
"HEADER": "Custom Attributes",
|
||||
"CONVERSATION": "Conversation",
|
||||
"CONTACT": "Contact"
|
||||
},
|
||||
"LIST": {
|
||||
@@ -84,15 +84,15 @@
|
||||
"Naam",
|
||||
"Beschrijving",
|
||||
"Type",
|
||||
"Toets"
|
||||
"Key"
|
||||
],
|
||||
"BUTTONS": {
|
||||
"EDIT": "Bewerken",
|
||||
"DELETE": "Verwijderen"
|
||||
},
|
||||
"EMPTY_RESULT": {
|
||||
"404": "Er zijn geen aangepaste attributen aangemaakt",
|
||||
"NOT_FOUND": "Er zijn geen aangepaste attributen geconfigureerd"
|
||||
"404": "There are no custom attributes created",
|
||||
"NOT_FOUND": "There are no custom attributes configured"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,71 +1,71 @@
|
||||
{
|
||||
"AUDIT_LOGS": {
|
||||
"HEADER": "Audit Logs",
|
||||
"HEADER_BTN_TXT": "Audit-logs toevoegen",
|
||||
"LOADING": "Ophalen van Audit Logs",
|
||||
"HEADER_BTN_TXT": "Add Audit Logs",
|
||||
"LOADING": "Fetching Audit Logs",
|
||||
"SEARCH_404": "Er zijn geen items die overeenkomen met deze zoekopdracht",
|
||||
"SIDEBAR_TXT": "<p><b>Audit Logs</b> </p><p> Audit Logs zijn sporen voor gebeurtenissen en acties in een Chatwoot Systeem. </p>",
|
||||
"SIDEBAR_TXT": "<p><b>Audit Logs</b> </p><p> Audit Logs are trails for events and actions in a Chatwoot System. </p>",
|
||||
"LIST": {
|
||||
"404": "Er zijn geen Audit Logs beschikbaar in dit account.",
|
||||
"TITLE": "Beheer Audit Logs",
|
||||
"DESC": "Audit Logs zijn sporen voor gebeurtenissen en acties in een Chatwoot Systeem.",
|
||||
"404": "There are no Audit Logs available in this account.",
|
||||
"TITLE": "Manage Audit Logs",
|
||||
"DESC": "Audit Logs are trails for events and actions in a Chatwoot System.",
|
||||
"TABLE_HEADER": [
|
||||
"User",
|
||||
"Tijd",
|
||||
"Action",
|
||||
"IP-adres"
|
||||
]
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "AuditLogs succesvol opgehaald",
|
||||
"SUCCESS_MESSAGE": "AuditLogs retrieved successfully",
|
||||
"ERROR_MESSAGE": "Kan geen verbinding maken met Woot Server, probeer het later opnieuw"
|
||||
},
|
||||
"DEFAULT_USER": "Systeem",
|
||||
"DEFAULT_USER": "System",
|
||||
"AUTOMATION_RULE": {
|
||||
"ADD": "%{agentName} heeft een nieuwe automatiseringsregel aangemaakt (#%{id})",
|
||||
"EDIT": "%{agentName} heeft een automatiseringsregel bijgewerkt (#%{id})",
|
||||
"DELETE": "%{agentName} heeft een automatiseringsregel verwijderd (#%{id})"
|
||||
"ADD": "%{agentName} created a new automation rule (#%{id})",
|
||||
"EDIT": "%{agentName} updated an automation rule (#%{id})",
|
||||
"DELETE": "%{agentName} deleted an automation rule (#%{id})"
|
||||
},
|
||||
"ACCOUNT_USER": {
|
||||
"ADD": "%{agentName} heeft %{invitee} uitgenodigd voor het account als een %{role}",
|
||||
"ADD": "%{agentName} invited %{invitee} to the account as an %{role}",
|
||||
"EDIT": {
|
||||
"SELF": "%{agentName} heeft zijn %{attributes} gewijzigd naar %{values}",
|
||||
"OTHER": "%{agentName} heeft %{attributes} van %{user} gewijzigd naar %{values}"
|
||||
"SELF": "%{agentName} changed their %{attributes} to %{values}",
|
||||
"OTHER": "%{agentName} changed %{attributes} of %{user} to %{values}"
|
||||
}
|
||||
},
|
||||
"INBOX": {
|
||||
"ADD": "%{agentName} heeft een nieuwe inbox aangemaakt (#%{id})",
|
||||
"EDIT": "%{agentName} heeft een inbox bijgewerkt (#%{id})",
|
||||
"DELETE": "%{agentName} heeft een inbox verwijderd (#%{id})"
|
||||
"ADD": "%{agentName} created a new inbox (#%{id})",
|
||||
"EDIT": "%{agentName} updated an inbox (#%{id})",
|
||||
"DELETE": "%{agentName} deleted an inbox (#%{id})"
|
||||
},
|
||||
"WEBHOOK": {
|
||||
"ADD": "%{agentName} heeft een nieuwe webhook aangemaakt (#%{id})",
|
||||
"EDIT": "%{agentName} heeft een webhook bijgewerkt (#%{id})",
|
||||
"DELETE": "%{agentName} heeft een webhook verwijderd (#%{id})"
|
||||
"ADD": "%{agentName} created a new webhook (#%{id})",
|
||||
"EDIT": "%{agentName} updated a webhook (#%{id})",
|
||||
"DELETE": "%{agentName} deleted a webhook (#%{id})"
|
||||
},
|
||||
"USER_ACTION": {
|
||||
"SIGN_IN": "%{agentName} is ingelogd",
|
||||
"SIGN_OUT": "%{agentName} heeft zich afgemeld"
|
||||
"SIGN_IN": "%{agentName} signed in",
|
||||
"SIGN_OUT": "%{agentName} signed out"
|
||||
},
|
||||
"TEAM": {
|
||||
"ADD": "%{agentName} heeft een nieuw team aangemaakt (#%{id})",
|
||||
"EDIT": "%{agentName} heeft een team bijgewerkt (#%{id})",
|
||||
"DELETE": "%{agentName} heeft een team verwijderd (#%{id})"
|
||||
"ADD": "%{agentName} created a new team (#%{id})",
|
||||
"EDIT": "%{agentName} updated a team (#%{id})",
|
||||
"DELETE": "%{agentName} deleted a team (#%{id})"
|
||||
},
|
||||
"MACRO": {
|
||||
"ADD": "%{agentName} heeft een nieuwe macro aangemaakt (#%{id})",
|
||||
"EDIT": "%{agentName} heeft een macro bijgewerkt (#%{id})",
|
||||
"DELETE": "%{agentName} heeft een macro verwijderd (#%{id})"
|
||||
"ADD": "%{agentName} created a new macro (#%{id})",
|
||||
"EDIT": "%{agentName} updated a macro (#%{id})",
|
||||
"DELETE": "%{agentName} deleted a macro (#%{id})"
|
||||
},
|
||||
"INBOX_MEMBER": {
|
||||
"ADD": "%{agentName} heeft %{user} toegevoegd aan de inbox (#%{inbox_id})",
|
||||
"REMOVE": "%{agentName} heeft %{user} verwijderd uit de inbox (#%{inbox_id})"
|
||||
"ADD": "%{agentName} added %{user} to the inbox(#%{inbox_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the inbox(#%{inbox_id})"
|
||||
},
|
||||
"TEAM_MEMBER": {
|
||||
"ADD": "%{agentName} heeft %{user} toegevoegd aan het team (#%{team_id})",
|
||||
"REMOVE": "%{agentName} heeft %{user} verwijderd van het team (#%{team_id})"
|
||||
"ADD": "%{agentName} added %{user} to the team(#%{team_id})",
|
||||
"REMOVE": "%{agentName} removed %{user} from the team(#%{team_id})"
|
||||
},
|
||||
"ACCOUNT": {
|
||||
"EDIT": "%{agentName} heeft de accountconfiguratie bijgewerkt (#%{id})"
|
||||
"EDIT": "%{agentName} updated the account configuration (#%{id})"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,54 +1,54 @@
|
||||
{
|
||||
"AUTOMATION": {
|
||||
"HEADER": "Automatisering",
|
||||
"HEADER_BTN_TXT": "Automatiseringsregel toevoegen",
|
||||
"LOADING": "Automatiseringsregels ophalen",
|
||||
"SIDEBAR_TXT": "<p><b>Automatiseringsregels</b> <p>Automation kan bestaande processen vervangen en automatiseren die handmatige inspanning vereisen. U kunt veel dingen doen met automatisering, inclusief het toevoegen van labels en het toewijzen van gesprekken aan uw beste agent. Het team concentreert zich dus op wat ze het beste doen en besteedt minder tijd aan handmatige taken.</p>",
|
||||
"HEADER": "Automations",
|
||||
"HEADER_BTN_TXT": "Add Automation Rule",
|
||||
"LOADING": "Fetching automation rules",
|
||||
"SIDEBAR_TXT": "<p><b>Automation Rules</b> <p>Automation can replace and automate existing processes that require manual effort. You can do many things with automation, including adding labels and assigning conversation to the best agent. So the team focuses on what they do best and spends more little time on manual tasks.</p>",
|
||||
"ADD": {
|
||||
"TITLE": "Automatiseringsregel toevoegen",
|
||||
"TITLE": "Add Automation Rule",
|
||||
"SUBMIT": "Aanmaken",
|
||||
"CANCEL_BUTTON_TEXT": "Annuleren",
|
||||
"FORM": {
|
||||
"NAME": {
|
||||
"LABEL": "Regel naam",
|
||||
"PLACEHOLDER": "Voer regelnaam in",
|
||||
"ERROR": "Naam is vereist"
|
||||
"LABEL": "Rule Name",
|
||||
"PLACEHOLDER": "Enter rule name",
|
||||
"ERROR": "Name is required"
|
||||
},
|
||||
"DESC": {
|
||||
"LABEL": "Beschrijving",
|
||||
"PLACEHOLDER": "Voer regelomschrijving in",
|
||||
"ERROR": "Omschrijving is vereist"
|
||||
"PLACEHOLDER": "Enter rule description",
|
||||
"ERROR": "Description is required"
|
||||
},
|
||||
"EVENT": {
|
||||
"LABEL": "Event",
|
||||
"PLACEHOLDER": "Selecteer er een",
|
||||
"ERROR": "Event is verplicht"
|
||||
"PLACEHOLDER": "Please select one",
|
||||
"ERROR": "Event is required"
|
||||
},
|
||||
"CONDITIONS": {
|
||||
"LABEL": "Voorwaarden"
|
||||
"LABEL": "Conditions"
|
||||
},
|
||||
"ACTIONS": {
|
||||
"LABEL": "Acties"
|
||||
}
|
||||
},
|
||||
"CONDITION_BUTTON_LABEL": "Voorwaarde toevoegen",
|
||||
"ACTION_BUTTON_LABEL": "Actie toevoegen",
|
||||
"CONDITION_BUTTON_LABEL": "Add Condition",
|
||||
"ACTION_BUTTON_LABEL": "Add Action",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Automatiseringsregel succesvol toegevoegd",
|
||||
"ERROR_MESSAGE": "Kan geen automatiseringsregel aanmaken, probeer het later opnieuw"
|
||||
"SUCCESS_MESSAGE": "Automation rule added successfully",
|
||||
"ERROR_MESSAGE": "Could not able to create a automation rule, Please try again later"
|
||||
}
|
||||
},
|
||||
"LIST": {
|
||||
"TABLE_HEADER": [
|
||||
"Naam",
|
||||
"Beschrijving",
|
||||
"Actief",
|
||||
"Aangemaakt op"
|
||||
"Active",
|
||||
"Created on"
|
||||
],
|
||||
"404": "Geen automatiseringsregels gevonden"
|
||||
"404": "No automation rules found"
|
||||
},
|
||||
"DELETE": {
|
||||
"TITLE": "Automatiseringsregel verwijderen",
|
||||
"TITLE": "Delete Automation Rule",
|
||||
"SUBMIT": "Verwijderen",
|
||||
"CANCEL_BUTTON_TEXT": "Annuleren",
|
||||
"CONFIRM": {
|
||||
@@ -58,24 +58,24 @@
|
||||
"NO": "Nee, Behouden "
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Automatiseringsregel succesvol verwijderd",
|
||||
"ERROR_MESSAGE": "Kon geen automatiseringsregel verwijderen, probeer het later opnieuw"
|
||||
"SUCCESS_MESSAGE": "Automation rule deleted successfully",
|
||||
"ERROR_MESSAGE": "Could not able to delete a automation rule, Please try again later"
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"TITLE": "Automatiseringsregel bewerken",
|
||||
"TITLE": "Edit Automation Rule",
|
||||
"SUBMIT": "Vernieuwen",
|
||||
"CANCEL_BUTTON_TEXT": "Annuleren",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Automatiseringsregel succesvol bijgewerkt",
|
||||
"ERROR_MESSAGE": "Automatiseringsregel kon niet worden bijgewerkt, probeer het later opnieuw"
|
||||
"SUCCESS_MESSAGE": "Automation rule updated successfully",
|
||||
"ERROR_MESSAGE": "Could not update automation rule, Please try again later"
|
||||
}
|
||||
},
|
||||
"CLONE": {
|
||||
"TOOLTIP": "Kloon",
|
||||
"TOOLTIP": "Clone",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Automatisering met succes gekloond",
|
||||
"ERROR_MESSAGE": "Automatiseringsregel kon niet worden gekloond, probeer het later opnieuw"
|
||||
"SUCCESS_MESSAGE": "Automation cloned successfully",
|
||||
"ERROR_MESSAGE": "Could not clone automation rule, Please try again later"
|
||||
}
|
||||
},
|
||||
"FORM": {
|
||||
@@ -83,36 +83,36 @@
|
||||
"CREATE": "Aanmaken",
|
||||
"DELETE": "Verwijderen",
|
||||
"CANCEL": "Annuleren",
|
||||
"RESET_MESSAGE": "Het veranderen van het event type zal de condities en events die u hieronder hebt toegevoegd resetten"
|
||||
"RESET_MESSAGE": "Changing event type will reset the conditions and events you have added below"
|
||||
},
|
||||
"CONDITION": {
|
||||
"DELETE_MESSAGE": "U moet tenminste één voorwaarde hebben om op te slaan",
|
||||
"CONTACT_CUSTOM_ATTR_LABEL": "Aangepaste contactattributen",
|
||||
"CONVERSATION_CUSTOM_ATTR_LABEL": "Gesprek aangepaste attributen"
|
||||
"DELETE_MESSAGE": "You need to have atleast one condition to save",
|
||||
"CONTACT_CUSTOM_ATTR_LABEL": "Contact Custom Attributes",
|
||||
"CONVERSATION_CUSTOM_ATTR_LABEL": "Conversation Custom Attributes"
|
||||
},
|
||||
"ACTION": {
|
||||
"DELETE_MESSAGE": "U moet tenminste één actie hebben om op te slaan",
|
||||
"TEAM_MESSAGE_INPUT_PLACEHOLDER": "Voer hier uw bericht in",
|
||||
"TEAM_DROPDOWN_PLACEHOLDER": "Teams selecteren"
|
||||
"DELETE_MESSAGE": "You need to have atleast one action to save",
|
||||
"TEAM_MESSAGE_INPUT_PLACEHOLDER": "Enter your message here",
|
||||
"TEAM_DROPDOWN_PLACEHOLDER": "Select teams"
|
||||
},
|
||||
"TOGGLE": {
|
||||
"ACTIVATION_TITLE": "Automatiseringsregel activeren",
|
||||
"DEACTIVATION_TITLE": "Automatiseringsregel deactiveren",
|
||||
"ACTIVATION_DESCRIPTION": "Deze actie activeert de automatiseringsregel '{automationName}'. Weet u zeker dat u wilt doorgaan?",
|
||||
"DEACTIVATION_DESCRIPTION": "Deze actie zal de automatiseringsregel '{automationName}' deactiveren. Weet u zeker dat u wilt doorgaan?",
|
||||
"ACTIVATION_SUCCESFUL": "Automatiseringsregel succesvol geactiveerd",
|
||||
"DEACTIVATION_SUCCESFUL": "Automatiseringsregel succesvol gedeactiveerd",
|
||||
"ACTIVATION_ERROR": "Automatisering kon niet worden geactiveerd, probeer het later opnieuw",
|
||||
"DEACTIVATION_ERROR": "Automatisering kon niet worden gedeactiveerd, probeer het later opnieuw",
|
||||
"CONFIRMATION_LABEL": "Ja",
|
||||
"CANCEL_LABEL": "Nee"
|
||||
"ACTIVATION_TITLE": "Activate Automation Rule",
|
||||
"DEACTIVATION_TITLE": "Deactivate Automation Rule",
|
||||
"ACTIVATION_DESCRIPTION": "This action will activate the automation rule '{automationName}'. Are you sure you want to proceed?",
|
||||
"DEACTIVATION_DESCRIPTION": "This action will deactivate the automation rule '{automationName}'. Are you sure you want to proceed?",
|
||||
"ACTIVATION_SUCCESFUL": "Automation Rule Activated Successfully",
|
||||
"DEACTIVATION_SUCCESFUL": "Automation Rule Deactivated Successfully",
|
||||
"ACTIVATION_ERROR": "Could not Activate Automation, Please try again later",
|
||||
"DEACTIVATION_ERROR": "Could not Deactivate Automation, Please try again later",
|
||||
"CONFIRMATION_LABEL": "Yes",
|
||||
"CANCEL_LABEL": "No"
|
||||
},
|
||||
"ATTACHMENT": {
|
||||
"UPLOAD_ERROR": "Kon bijlage niet uploaden, probeer het opnieuw",
|
||||
"LABEL_IDLE": "Bijlage uploaden",
|
||||
"UPLOAD_ERROR": "Could not upload attachment, Please try again",
|
||||
"LABEL_IDLE": "Upload Attachment",
|
||||
"LABEL_UPLOADING": "Uploaden...",
|
||||
"LABEL_UPLOADED": "Succesvol geüpload",
|
||||
"LABEL_UPLOAD_FAILED": "Upload mislukt"
|
||||
"LABEL_UPLOADED": "Successfully Uploaded",
|
||||
"LABEL_UPLOAD_FAILED": "Upload Failed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
{
|
||||
"BULK_ACTION": {
|
||||
"CONVERSATIONS_SELECTED": "%{conversationCount} gesprekken geselecteerd",
|
||||
"CONVERSATIONS_SELECTED": "%{conversationCount} conversations selected",
|
||||
"AGENT_SELECT_LABEL": "Selecteer medewerker",
|
||||
"ASSIGN_CONFIRMATION_LABEL": "Weet u zeker dat u %{conversationCount} %{conversationLabel} wilt toewijzen aan",
|
||||
"UNASSIGN_CONFIRMATION_LABEL": "Weet u zeker dat u %{conversationCount} %{conversationLabel} wilt ontkoppelen?",
|
||||
"GO_BACK_LABEL": "Ga terug",
|
||||
"ASSIGN_CONFIRMATION_LABEL": "Are you sure to assign %{conversationCount} %{conversationLabel} to",
|
||||
"UNASSIGN_CONFIRMATION_LABEL": "Are you sure to unassign %{conversationCount} %{conversationLabel}?",
|
||||
"GO_BACK_LABEL": "Go back",
|
||||
"ASSIGN_LABEL": "Toewijzen",
|
||||
"YES": "Ja",
|
||||
"ASSIGN_AGENT_TOOLTIP": "Agent toewijzen",
|
||||
"ASSIGN_TEAM_TOOLTIP": "Team toewijzen",
|
||||
"ASSIGN_SUCCESFUL": "Gesprekken succesvol toegewezen",
|
||||
"ASSIGN_FAILED": "Toewijzen van gesprekken mislukt, probeer het opnieuw",
|
||||
"RESOLVE_SUCCESFUL": "Gesprekken succesvol opgehaald",
|
||||
"RESOLVE_FAILED": "Fout bij het ophalen van gesprekken, probeer het opnieuw",
|
||||
"ALL_CONVERSATIONS_SELECTED_ALERT": "Gesprekken die op deze pagina zichtbaar zijn, zijn alleen geselecteerd.",
|
||||
"AGENT_LIST_LOADING": "Agents worden geladen",
|
||||
"YES": "Yes",
|
||||
"ASSIGN_AGENT_TOOLTIP": "Assign Agent",
|
||||
"ASSIGN_TEAM_TOOLTIP": "Assign team",
|
||||
"ASSIGN_SUCCESFUL": "Conversations assigned successfully",
|
||||
"ASSIGN_FAILED": "Failed to assign conversations, please try again",
|
||||
"RESOLVE_SUCCESFUL": "Conversations resolved successfully",
|
||||
"RESOLVE_FAILED": "Failed to resolve conversations, please try again",
|
||||
"ALL_CONVERSATIONS_SELECTED_ALERT": "Conversations visible on this page are only selected.",
|
||||
"AGENT_LIST_LOADING": "Loading Agents",
|
||||
"UPDATE": {
|
||||
"CHANGE_STATUS": "Status wijzigen",
|
||||
"SNOOZE_UNTIL_NEXT_REPLY": "Snooze tot het volgende antwoord",
|
||||
"UPDATE_SUCCESFUL": "Gespreksstatus succesvol bijgewerkt.",
|
||||
"UPDATE_FAILED": "Update van gesprekken mislukt, probeer het opnieuw"
|
||||
"CHANGE_STATUS": "Change status",
|
||||
"SNOOZE_UNTIL_NEXT_REPLY": "Snooze until next reply",
|
||||
"UPDATE_SUCCESFUL": "Conversation status updated successfully.",
|
||||
"UPDATE_FAILED": "Failed to update conversations, please try again"
|
||||
},
|
||||
"LABELS": {
|
||||
"ASSIGN_LABELS": "Label toewijzen",
|
||||
"NO_LABELS_FOUND": "Geen labels gevonden voor",
|
||||
"ASSIGN_SELECTED_LABELS": "Geselecteerde labels toewijzen",
|
||||
"ASSIGN_SUCCESFUL": "Labels met succes toegewezen",
|
||||
"ASSIGN_FAILED": "Toewijzen van labels mislukt, probeer het opnieuw"
|
||||
"ASSIGN_LABELS": "Assign Labels",
|
||||
"NO_LABELS_FOUND": "No labels found for",
|
||||
"ASSIGN_SELECTED_LABELS": "Assign selected labels",
|
||||
"ASSIGN_SUCCESFUL": "Labels assigned successfully",
|
||||
"ASSIGN_FAILED": "Failed to assign labels, please try again"
|
||||
},
|
||||
"TEAMS": {
|
||||
"TEAM_SELECT_LABEL": "Selecteer team",
|
||||
"NONE": "Geen",
|
||||
"NO_TEAMS_AVAILABLE": "Er zijn nog geen teams toegevoegd aan dit account.",
|
||||
"ASSIGN_SELECTED_TEAMS": "Geselecteerde team toewijzen",
|
||||
"ASSIGN_SUCCESFUL": "Teams succesvol toegewezen",
|
||||
"ASSIGN_FAILED": "Toewijzen team mislukt, probeer het opnieuw"
|
||||
"NO_TEAMS_AVAILABLE": "There are no teams added to this account yet.",
|
||||
"ASSIGN_SELECTED_TEAMS": "Assign selected team",
|
||||
"ASSIGN_SUCCESFUL": "Teams assiged successfully",
|
||||
"ASSIGN_FAILED": "Failed to assign team, please try again"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,26 +1,26 @@
|
||||
{
|
||||
"CAMPAIGN": {
|
||||
"HEADER": "Campagnes",
|
||||
"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": {
|
||||
"ONE_OFF": "Creëer een eenmalige campagne",
|
||||
"ONGOING": "Creëer een lopende campagne"
|
||||
"ONE_OFF": "Create a one off campaign",
|
||||
"ONGOING": "Create a ongoing campaign"
|
||||
},
|
||||
"ADD": {
|
||||
"TITLE": "Creëer een campagne",
|
||||
"TITLE": "Create a campaign",
|
||||
"DESC": "Proactive messages allow the customer to send outbound messages to their contacts which would trigger more conversations.",
|
||||
"CANCEL_BUTTON_TEXT": "Annuleren",
|
||||
"CREATE_BUTTON_TEXT": "Aanmaken",
|
||||
"FORM": {
|
||||
"TITLE": {
|
||||
"LABEL": "Titel",
|
||||
"LABEL": "Title",
|
||||
"PLACEHOLDER": "Please enter the title of campaign",
|
||||
"ERROR": "Title is required"
|
||||
},
|
||||
"SCHEDULED_AT": {
|
||||
"LABEL": "Scheduled time",
|
||||
"PLACEHOLDER": "Please select the time",
|
||||
"CONFIRM": "Bevestigen",
|
||||
"CONFIRM": "Confirm",
|
||||
"ERROR": "Scheduled time is required"
|
||||
},
|
||||
"AUDIENCE": {
|
||||
@@ -29,8 +29,8 @@
|
||||
"ERROR": "Audience is required"
|
||||
},
|
||||
"INBOX": {
|
||||
"LABEL": "Selecteer een inbox",
|
||||
"PLACEHOLDER": "Selecteer een inbox",
|
||||
"LABEL": "Select Inbox",
|
||||
"PLACEHOLDER": "Select Inbox",
|
||||
"ERROR": "Inbox is required"
|
||||
},
|
||||
"MESSAGE": {
|
||||
@@ -87,7 +87,7 @@
|
||||
"LOADING_MESSAGE": "Loading campaigns...",
|
||||
"404": "There are no campaigns created for this inbox.",
|
||||
"TABLE_HEADER": {
|
||||
"TITLE": "Titel",
|
||||
"TITLE": "Title",
|
||||
"MESSAGE": "Bericht",
|
||||
"INBOX": "Postvak In",
|
||||
"STATUS": "Status",
|
||||
@@ -119,8 +119,8 @@
|
||||
},
|
||||
"ONGOING": {
|
||||
"HEADER": "Ongoing campaigns",
|
||||
"404": "Er zijn geen lopende campagnes aangemaakt",
|
||||
"INBOXES_NOT_FOUND": "Maak een website inbox en begin met het toevoegen van campagnes"
|
||||
"404": "There are no ongoing campaigns created",
|
||||
"INBOXES_NOT_FOUND": "Please create an website inbox and start adding campaigns"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"HEADER_BTN_TXT": "Standaard antwoord toevoegen",
|
||||
"LOADING": "Standaard antwoorden ophalen",
|
||||
"SEARCH_404": "Er zijn geen items die overeenkomen met deze zoekopdracht",
|
||||
"SIDEBAR_TXT": "<p><b> Standaard antwoorden</b> </p><p> Standaard antwoorden zijn opgeslagen antwoordsjablonen die kunnen worden gebruikt om snel een antwoord te sturen naar een gesprek. </p><p> Voor het maken van een standaard antwoord, klik op de <b>Voeg standaard antwoord toe</b>. U kunt een bestaand antwoord bewerken of verwijderen door op de knop Bewerken of Verwijderen te klikken </p><p> Standaard antwoorden worden gebruikt met behulp van <b>korte codes</b>. Medewerkers hebben toegang tot standaard antwoorden in een chat door <b>'/'</b> te typen gevolgd door de korte code. </p>",
|
||||
"SIDEBAR_TXT": "<p><b>Canned Responses</b> </p><p> Canned Responses are saved reply templates which can be used to quickly send out a reply to a conversation. </p><p> For creating a Canned Response, just click on the <b>Add Canned Response</b>. You can also edit or delete an existing Canned Response by clicking on the Edit or Delete button </p><p> Canned responses are used with the help of <b>Short Codes</b>. Agents can access canned responses while on a chat by typing <b>'/'</b> followed by the short code. </p>",
|
||||
"LIST": {
|
||||
"404": "Er zijn geen standaard antwoorden beschikbaar in dit account.",
|
||||
"TITLE": "Beheer standaard antwoorden",
|
||||
@@ -17,7 +17,7 @@
|
||||
},
|
||||
"ADD": {
|
||||
"TITLE": "Standaard antwoord toevoegen",
|
||||
"DESC": "Standaard antwoorden zijn opgeslagen antwoordsjablonen die kunnen worden gebruikt om snel te reageren op gesprekken.",
|
||||
"DESC": "Canned Responses are saved reply templates which can be used to quickly send out reply to conversation.",
|
||||
"CANCEL_BUTTON_TEXT": "Annuleren",
|
||||
"FORM": {
|
||||
"SHORT_CODE": {
|
||||
|
||||
@@ -36,23 +36,23 @@
|
||||
}
|
||||
},
|
||||
"VIEW_FILTER": "Bekijken",
|
||||
"SORT_TOOLTIP_LABEL": "Sorteer gesprekken",
|
||||
"SORT_TOOLTIP_LABEL": "Sort conversations",
|
||||
"CHAT_SORT": {
|
||||
"STATUS": "Status",
|
||||
"ORDER_BY": "Sorteren op"
|
||||
"ORDER_BY": "Order by"
|
||||
},
|
||||
"CHAT_SORT_FILTER_ITEMS": {
|
||||
"latest": {
|
||||
"TEXT": "Laatste Activiteit"
|
||||
"TEXT": "Last activity"
|
||||
},
|
||||
"sort_on_created_at": {
|
||||
"TEXT": "Aangemaakt op"
|
||||
"TEXT": "Created at"
|
||||
},
|
||||
"sort_on_priority": {
|
||||
"TEXT": "Prioriteit"
|
||||
"TEXT": "Priority"
|
||||
},
|
||||
"sort_on_waiting_since": {
|
||||
"TEXT": "Wachtend op antwoord"
|
||||
"TEXT": "Pending Response"
|
||||
}
|
||||
},
|
||||
"ATTACHMENTS": {
|
||||
@@ -76,20 +76,20 @@
|
||||
}
|
||||
},
|
||||
"CHAT_SORT_BY_FILTER": {
|
||||
"TITLE": "Gesprek sorteren",
|
||||
"DROPDOWN_TITLE": "Sorteren op",
|
||||
"TITLE": "Sort conversation",
|
||||
"DROPDOWN_TITLE": "Sort by",
|
||||
"ITEMS": {
|
||||
"LATEST": {
|
||||
"NAME": "Laatste activiteit om",
|
||||
"LABEL": "Laatste Activiteit"
|
||||
"NAME": "Last activity at",
|
||||
"LABEL": "Last activity"
|
||||
},
|
||||
"CREATED_AT": {
|
||||
"NAME": "Aangemaakt op",
|
||||
"LABEL": "Aangemaakt op"
|
||||
"NAME": "Created at",
|
||||
"LABEL": "Created at"
|
||||
},
|
||||
"LAST_USER_MESSAGE_AT": {
|
||||
"NAME": "Laatste bericht van gebruiker op",
|
||||
"LABEL": "Laatste bericht"
|
||||
"NAME": "Last user message at",
|
||||
"LABEL": "Last message"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
"SUBMIT": "Importeren",
|
||||
"CANCEL": "Annuleren"
|
||||
},
|
||||
"SUCCESS_MESSAGE": "U wordt per e-mail geïnformeerd wanneer de import is voltooid.",
|
||||
"SUCCESS_MESSAGE": "You will be notified via email when the import is complete.",
|
||||
"ERROR_MESSAGE": "Er is een fout opgetreden, probeer het opnieuw"
|
||||
},
|
||||
"EXPORT_CONTACTS": {
|
||||
|
||||
@@ -2,17 +2,17 @@
|
||||
"CONTACTS_FILTER": {
|
||||
"TITLE": "Contacten filteren",
|
||||
"SUBTITLE": "Voeg hieronder filters toe en druk op 'Verzenden' om contacten te filteren.",
|
||||
"EDIT_CUSTOM_SEGMENT": "Bewerk Segment",
|
||||
"CUSTOM_VIEWS_SUBTITLE": "Filters toevoegen of verwijderen en bijwerken van uw segment.",
|
||||
"EDIT_CUSTOM_SEGMENT": "Edit Segment",
|
||||
"CUSTOM_VIEWS_SUBTITLE": "Add or remove filters and update your segment.",
|
||||
"ADD_NEW_FILTER": "Filter toevoegen",
|
||||
"CLEAR_ALL_FILTERS": "Alle filters wissen",
|
||||
"FILTER_DELETE_ERROR": "U moet minstens één filter hebben om op te slaan",
|
||||
"SUBMIT_BUTTON_LABEL": "Bevestigen",
|
||||
"UPDATE_BUTTON_LABEL": "Segment Updaten",
|
||||
"UPDATE_BUTTON_LABEL": "Update Segment",
|
||||
"CANCEL_BUTTON_LABEL": "Annuleren",
|
||||
"CLEAR_BUTTON_LABEL": "Filters wissen",
|
||||
"EMPTY_VALUE_ERROR": "Waarde is vereist",
|
||||
"SEGMENT_LABEL": "Segment Naam",
|
||||
"SEGMENT_LABEL": "Segment Name",
|
||||
"SEGMENT_QUERY_LABEL": "Segment Query",
|
||||
"TOOLTIP_LABEL": "Contacten filteren",
|
||||
"QUERY_DROPDOWN_LABELS": {
|
||||
|
||||
@@ -79,7 +79,7 @@
|
||||
"CANCEL": "Annuleren"
|
||||
},
|
||||
"PRIORITY": {
|
||||
"TITLE": "Prioriteit",
|
||||
"TITLE": "Priority",
|
||||
"OPTIONS": {
|
||||
"NONE": "Geen",
|
||||
"URGENT": "Urgent",
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
"TITLE": "Beoordeel uw gesprek",
|
||||
"PLACEHOLDER": "Vertel ons meer...",
|
||||
"RATINGS": {
|
||||
"POOR": "😞 Slecht",
|
||||
"FAIR": "😑 Redelijk",
|
||||
"AVERAGE": "😐 Gemiddeld",
|
||||
"GOOD": "😀 Goed",
|
||||
"EXCELLENT": "😍 Uitstekend"
|
||||
"POOR": "😞 Poor",
|
||||
"FAIR": "😑 Fair",
|
||||
"AVERAGE": "😐 Average",
|
||||
"GOOD": "😀 Good",
|
||||
"EXCELLENT": "😍 Excellent"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
"SECTIONS": {
|
||||
"GENERAL": "General",
|
||||
"REPORTS": "Rapporten",
|
||||
"CONVERSATION": "Gesprek",
|
||||
"CONVERSATION": "Conversation",
|
||||
"CHANGE_ASSIGNEE": "Change Assignee",
|
||||
"CHANGE_PRIORITY": "Change Priority",
|
||||
"CHANGE_TEAM": "Change Team",
|
||||
@@ -153,7 +153,7 @@
|
||||
"CHANGE_APPEARANCE": "Change Appearance",
|
||||
"LIGHT_MODE": "Light",
|
||||
"DARK_MODE": "Dark",
|
||||
"SYSTEM_MODE": "Systeem"
|
||||
"SYSTEM_MODE": "System"
|
||||
}
|
||||
},
|
||||
"DASHBOARD_APPS": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user