Merge branch 'release/2.15.0'
This commit is contained in:
@@ -102,8 +102,8 @@ jobs:
|
||||
echo "ERROR: The swagger.json file is not in sync with the yaml specification. Run 'rake swagger:build' and commit 'swagger/swagger.json'."
|
||||
exit 1
|
||||
fi
|
||||
curl -L https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/5.3.0/openapi-generator-cli-5.3.0.jar > ~/tmp/openapi-generator-cli-5.3.0.jar
|
||||
java -jar ~/tmp/openapi-generator-cli-5.3.0.jar validate -i swagger/swagger.json
|
||||
curl -L https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/6.3.0/openapi-generator-cli-6.3.0.jar > ~/tmp/openapi-generator-cli-6.3.0.jar
|
||||
java -jar ~/tmp/openapi-generator-cli-6.3.0.jar validate -i swagger/swagger.json
|
||||
|
||||
# Database setup
|
||||
- run: yarn install --check-files
|
||||
|
||||
@@ -30,7 +30,7 @@ RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
||||
npm
|
||||
|
||||
# Install rbenv and ruby
|
||||
ARG RUBY_VERSION="3.0.4"
|
||||
ARG RUBY_VERSION="3.1.3"
|
||||
RUN git clone https://github.com/rbenv/rbenv.git ~/.rbenv \
|
||||
&& echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc \
|
||||
&& echo 'eval "$(rbenv init -)"' >> ~/.bashrc
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
// 1025,8025 mailhog
|
||||
"forwardPorts": [8025, 3000, 3035],
|
||||
|
||||
"postCreateCommand": ".devcontainer/scripts/setup.sh && bundle exec rake db:chatwoot_prepare && yarn",
|
||||
"postCreateCommand": ".devcontainer/scripts/setup.sh && POSTGRES_STATEMENT_TIMEOUT=600s bundle exec rake db:chatwoot_prepare && yarn",
|
||||
"portsAttributes": {
|
||||
"3000": {
|
||||
"label": "Rails Server"
|
||||
|
||||
11
.env.example
11
.env.example
@@ -52,6 +52,8 @@ POSTGRES_HOST=postgres
|
||||
POSTGRES_USERNAME=postgres
|
||||
POSTGRES_PASSWORD=
|
||||
RAILS_ENV=development
|
||||
# Changes the Postgres query timeout limit. The default is 14 seconds. Modify only when required.
|
||||
# POSTGRES_STATEMENT_TIMEOUT=14s
|
||||
RAILS_MAX_THREADS=5
|
||||
|
||||
# The email from which all outgoing emails are sent
|
||||
@@ -169,6 +171,9 @@ USE_INBOX_AVATAR_FOR_BOT=true
|
||||
## Sentry
|
||||
# SENTRY_DSN=
|
||||
|
||||
## LogRocket
|
||||
# LOG_ROCKET_PROJECT_ID=xxxxx/some-project
|
||||
|
||||
## Scout
|
||||
## https://scoutapm.com/docs/ruby/configuration
|
||||
# SCOUT_KEY=YOURKEY
|
||||
@@ -186,11 +191,7 @@ USE_INBOX_AVATAR_FOR_BOT=true
|
||||
## https://github.com/DataDog/dd-trace-rb/blob/master/docs/GettingStarted.md#environment-variables
|
||||
# DD_TRACE_AGENT_URL=
|
||||
|
||||
## IP look up configuration
|
||||
## ref https://github.com/alexreisner/geocoder/blob/master/README_API_GUIDE.md
|
||||
## works only on accounts with ip look up feature enabled
|
||||
# IP_LOOKUP_SERVICE=geoip2
|
||||
# maxmindb api key to use geoip2 service
|
||||
# MaxMindDB API key to download GeoLite2 City database
|
||||
# IP_LOOKUP_API_KEY=
|
||||
|
||||
## Rack Attack configuration
|
||||
|
||||
6
.github/workflows/nightly_installer.yml
vendored
6
.github/workflows/nightly_installer.yml
vendored
@@ -44,3 +44,9 @@ jobs:
|
||||
# sudo systemctl restart chatwoot.target
|
||||
# curl http://localhost:3000/api
|
||||
|
||||
- name: Upload chatwoot setup log file as an artifact
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: chatwoot-setup-log-file
|
||||
path: /var/log/chatwoot-setup.log
|
||||
|
||||
13
.github/workflows/run_foss_spec.yml
vendored
13
.github/workflows/run_foss_spec.yml
vendored
@@ -15,7 +15,7 @@ on:
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
services:
|
||||
postgres:
|
||||
image: postgres:10.8
|
||||
@@ -49,6 +49,10 @@ jobs:
|
||||
with:
|
||||
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: yarn
|
||||
run: yarn install
|
||||
|
||||
@@ -70,3 +74,10 @@ jobs:
|
||||
- name: Run backend tests
|
||||
run: |
|
||||
bundle exec rspec --profile=10 --format documentation
|
||||
|
||||
- name: Upload rails log folder
|
||||
uses: actions/upload-artifact@v3
|
||||
if: always()
|
||||
with:
|
||||
name: rails-log-folder
|
||||
path: log
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
npx --no-install lint-staged
|
||||
|
||||
# lint only staged ruby files
|
||||
git diff --name-only --cached | xargs ls -1 2>/dev/null | grep '\.rb$' | xargs bundle exec rubocop -a
|
||||
git diff --name-only --cached | xargs ls -1 2>/dev/null | grep '\.rb$' | xargs bundle exec rubocop --force-exclusion -a
|
||||
|
||||
# stage rubocop changes to files
|
||||
git diff --name-only --cached | xargs git add
|
||||
|
||||
7
.vscode/settings.json
vendored
7
.vscode/settings.json
vendored
@@ -1 +1,6 @@
|
||||
{}
|
||||
{
|
||||
"cSpell.words": [
|
||||
"chatwoot",
|
||||
"dompurify"
|
||||
]
|
||||
}
|
||||
|
||||
21
Gemfile
21
Gemfile
@@ -4,7 +4,7 @@ ruby '3.1.3'
|
||||
|
||||
##-- base gems for rails --##
|
||||
gem 'rack-cors', require: 'rack/cors'
|
||||
gem 'rails', '~> 6.1', '>= 6.1.7.1'
|
||||
gem 'rails', '~> 6.1', '>= 6.1.7.3'
|
||||
# Reduces boot times through caching; required in config/boot.rb
|
||||
gem 'bootsnap', require: false
|
||||
|
||||
@@ -96,13 +96,16 @@ gem 'slack-ruby-client'
|
||||
gem 'google-cloud-dialogflow'
|
||||
|
||||
##-- apm and error monitoring ---#
|
||||
gem 'ddtrace'
|
||||
gem 'elastic-apm'
|
||||
gem 'newrelic_rpm'
|
||||
gem 'scout_apm'
|
||||
gem 'sentry-rails', '~> 5.3', '>= 5.3.1'
|
||||
gem 'sentry-ruby', '~> 5.3'
|
||||
gem 'sentry-sidekiq', '~> 5.3', '>= 5.3.1'
|
||||
# loaded only when environment variables are set.
|
||||
# ref application.rb
|
||||
gem 'ddtrace', require: false
|
||||
gem 'elastic-apm', require: false
|
||||
gem 'newrelic_rpm', require: false
|
||||
gem 'newrelic-sidekiq-metrics', require: false
|
||||
gem 'scout_apm', require: false
|
||||
gem 'sentry-rails', require: false
|
||||
gem 'sentry-ruby', require: false
|
||||
gem 'sentry-sidekiq', require: false
|
||||
|
||||
##-- background job processing --##
|
||||
gem 'sidekiq', '~> 6.4.2'
|
||||
@@ -203,6 +206,8 @@ end
|
||||
# worked with microsoft refresh token
|
||||
gem 'omniauth-oauth2'
|
||||
|
||||
gem 'audited', '~> 5.2'
|
||||
|
||||
# need for google auth
|
||||
gem 'omniauth'
|
||||
gem 'omniauth-google-oauth2'
|
||||
|
||||
162
Gemfile.lock
162
Gemfile.lock
@@ -9,63 +9,63 @@ GIT
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (6.1.7.1)
|
||||
actionpack (= 6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
actioncable (6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.1.7.1)
|
||||
actionpack (= 6.1.7.1)
|
||||
activejob (= 6.1.7.1)
|
||||
activerecord (= 6.1.7.1)
|
||||
activestorage (= 6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
actionmailbox (6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
activejob (= 6.1.7.3)
|
||||
activerecord (= 6.1.7.3)
|
||||
activestorage (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.1.7.1)
|
||||
actionpack (= 6.1.7.1)
|
||||
actionview (= 6.1.7.1)
|
||||
activejob (= 6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
actionmailer (6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
actionview (= 6.1.7.3)
|
||||
activejob (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.1.7.1)
|
||||
actionview (= 6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
actionpack (6.1.7.3)
|
||||
actionview (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
rack (~> 2.0, >= 2.0.9)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.1.7.1)
|
||||
actionpack (= 6.1.7.1)
|
||||
activerecord (= 6.1.7.1)
|
||||
activestorage (= 6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
actiontext (6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
activerecord (= 6.1.7.3)
|
||||
activestorage (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
actionview (6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.1, >= 1.2.0)
|
||||
active_record_query_trace (1.8)
|
||||
activejob (6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
activejob (6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
activerecord (6.1.7.1)
|
||||
activemodel (= 6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
activemodel (6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
activerecord (6.1.7.3)
|
||||
activemodel (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
activerecord-import (1.4.0)
|
||||
activerecord (>= 4.2)
|
||||
activestorage (6.1.7.1)
|
||||
actionpack (= 6.1.7.1)
|
||||
activejob (= 6.1.7.1)
|
||||
activerecord (= 6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
activestorage (6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
activejob (= 6.1.7.3)
|
||||
activerecord (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
marcel (~> 1.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (6.1.7.1)
|
||||
activesupport (6.1.7.3)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
@@ -90,6 +90,8 @@ GEM
|
||||
rake (>= 10.4, < 14.0)
|
||||
ast (2.4.2)
|
||||
attr_extras (6.2.5)
|
||||
audited (5.2.0)
|
||||
activerecord (>= 5.0, < 7.1)
|
||||
aws-eventstream (1.2.0)
|
||||
aws-partitions (1.605.0)
|
||||
aws-sdk-core (3.131.2)
|
||||
@@ -136,7 +138,7 @@ GEM
|
||||
climate_control (1.1.1)
|
||||
coderay (1.1.3)
|
||||
commonmarker (0.23.7)
|
||||
concurrent-ruby (1.1.10)
|
||||
concurrent-ruby (1.2.2)
|
||||
connection_pool (2.2.5)
|
||||
crack (0.4.5)
|
||||
rexml
|
||||
@@ -187,7 +189,7 @@ GEM
|
||||
concurrent-ruby (~> 1.0)
|
||||
http (>= 3.0)
|
||||
email_reply_trimmer (0.1.13)
|
||||
erubi (1.10.0)
|
||||
erubi (1.12.0)
|
||||
et-orbi (1.2.7)
|
||||
tzinfo
|
||||
execjs (2.8.1)
|
||||
@@ -248,7 +250,7 @@ GEM
|
||||
grpc (~> 1.36)
|
||||
geocoder (1.8.0)
|
||||
gli (2.21.0)
|
||||
globalid (1.0.1)
|
||||
globalid (1.1.0)
|
||||
activesupport (>= 5.0)
|
||||
gmail_xoauth (0.4.2)
|
||||
oauth (>= 0.3.6)
|
||||
@@ -353,7 +355,7 @@ GEM
|
||||
mini_mime (>= 1.0.0)
|
||||
multi_xml (>= 0.5.2)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.11.0)
|
||||
i18n (1.12.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
image_processing (1.12.2)
|
||||
mini_magick (>= 4.9.5, < 5)
|
||||
@@ -417,8 +419,11 @@ GEM
|
||||
loofah (2.19.1)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
mail (2.8.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
marcel (1.0.2)
|
||||
maxminddb (0.1.22)
|
||||
memoist (0.16.2)
|
||||
@@ -428,8 +433,8 @@ GEM
|
||||
mime-types-data (3.2022.0105)
|
||||
mini_magick (4.11.0)
|
||||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.8.0)
|
||||
minitest (5.16.2)
|
||||
mini_portile2 (2.8.1)
|
||||
minitest (5.18.0)
|
||||
mock_redis (0.32.0)
|
||||
ruby2_keywords
|
||||
momentjs-rails (2.29.1.1)
|
||||
@@ -449,16 +454,19 @@ GEM
|
||||
net-smtp (0.3.3)
|
||||
net-protocol
|
||||
netrc (0.11.0)
|
||||
newrelic-sidekiq-metrics (1.6.1)
|
||||
newrelic_rpm (~> 8)
|
||||
sidekiq
|
||||
newrelic_rpm (8.15.0)
|
||||
nio4r (2.5.8)
|
||||
nokogiri (1.13.10)
|
||||
nokogiri (1.14.2)
|
||||
mini_portile2 (~> 2.8.0)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.13.10-arm64-darwin)
|
||||
nokogiri (1.14.2-arm64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.13.10-x86_64-darwin)
|
||||
nokogiri (1.14.2-x86_64-darwin)
|
||||
racc (~> 1.4)
|
||||
nokogiri (1.13.10-x86_64-linux)
|
||||
nokogiri (1.14.2-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
oauth (0.5.10)
|
||||
oauth2 (2.0.9)
|
||||
@@ -506,8 +514,8 @@ GEM
|
||||
pundit (2.2.0)
|
||||
activesupport (>= 3.0.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.6.1)
|
||||
rack (2.2.6.2)
|
||||
racc (1.6.2)
|
||||
rack (2.2.6.4)
|
||||
rack-attack (6.6.1)
|
||||
rack (>= 1.0, < 3)
|
||||
rack-cors (1.1.1)
|
||||
@@ -516,32 +524,32 @@ GEM
|
||||
rack
|
||||
rack-proxy (0.7.2)
|
||||
rack
|
||||
rack-test (2.0.2)
|
||||
rack-test (2.1.0)
|
||||
rack (>= 1.3)
|
||||
rack-timeout (0.6.3)
|
||||
rails (6.1.7.1)
|
||||
actioncable (= 6.1.7.1)
|
||||
actionmailbox (= 6.1.7.1)
|
||||
actionmailer (= 6.1.7.1)
|
||||
actionpack (= 6.1.7.1)
|
||||
actiontext (= 6.1.7.1)
|
||||
actionview (= 6.1.7.1)
|
||||
activejob (= 6.1.7.1)
|
||||
activemodel (= 6.1.7.1)
|
||||
activerecord (= 6.1.7.1)
|
||||
activestorage (= 6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
rails (6.1.7.3)
|
||||
actioncable (= 6.1.7.3)
|
||||
actionmailbox (= 6.1.7.3)
|
||||
actionmailer (= 6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
actiontext (= 6.1.7.3)
|
||||
actionview (= 6.1.7.3)
|
||||
activejob (= 6.1.7.3)
|
||||
activemodel (= 6.1.7.3)
|
||||
activerecord (= 6.1.7.3)
|
||||
activestorage (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 6.1.7.1)
|
||||
railties (= 6.1.7.3)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-dom-testing (2.0.3)
|
||||
activesupport (>= 4.2.0)
|
||||
nokogiri (>= 1.6)
|
||||
rails-html-sanitizer (1.4.4)
|
||||
rails-html-sanitizer (1.5.0)
|
||||
loofah (~> 2.19, >= 2.19.1)
|
||||
railties (6.1.7.1)
|
||||
actionpack (= 6.1.7.1)
|
||||
activesupport (= 6.1.7.1)
|
||||
railties (6.1.7.3)
|
||||
actionpack (= 6.1.7.3)
|
||||
activesupport (= 6.1.7.3)
|
||||
method_source
|
||||
rake (>= 12.2)
|
||||
thor (~> 1.0)
|
||||
@@ -676,9 +684,9 @@ GEM
|
||||
spring-watcher-listen (2.0.1)
|
||||
listen (>= 2.7, < 4.0)
|
||||
spring (>= 1.2, < 3.0)
|
||||
sprockets (4.1.1)
|
||||
sprockets (4.2.0)
|
||||
concurrent-ruby (~> 1.0)
|
||||
rack (> 1, < 3)
|
||||
rack (>= 2.2.4, < 4)
|
||||
sprockets-rails (3.4.2)
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
@@ -701,7 +709,7 @@ GEM
|
||||
nokogiri (>= 1.6, < 2.0)
|
||||
twitty (0.1.4)
|
||||
oauth
|
||||
tzinfo (2.0.4)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
tzinfo-data (1.2022.1)
|
||||
tzinfo (>= 1.0.0)
|
||||
@@ -746,7 +754,7 @@ GEM
|
||||
working_hours (1.4.1)
|
||||
activesupport (>= 3.2)
|
||||
tzinfo
|
||||
zeitwerk (2.6.0)
|
||||
zeitwerk (2.6.7)
|
||||
|
||||
PLATFORMS
|
||||
arm64-darwin-20
|
||||
@@ -763,6 +771,7 @@ DEPENDENCIES
|
||||
administrate
|
||||
annotate
|
||||
attr_extras
|
||||
audited (~> 5.2)
|
||||
aws-sdk-s3
|
||||
azure-storage-blob
|
||||
barnes
|
||||
@@ -817,6 +826,7 @@ DEPENDENCIES
|
||||
net-imap
|
||||
net-pop
|
||||
net-smtp
|
||||
newrelic-sidekiq-metrics
|
||||
newrelic_rpm
|
||||
omniauth
|
||||
omniauth-google-oauth2
|
||||
@@ -831,7 +841,7 @@ DEPENDENCIES
|
||||
rack-attack
|
||||
rack-cors
|
||||
rack-timeout
|
||||
rails (~> 6.1, >= 6.1.7.1)
|
||||
rails (~> 6.1, >= 6.1.7.3)
|
||||
redis
|
||||
redis-namespace
|
||||
responders
|
||||
@@ -844,9 +854,9 @@ DEPENDENCIES
|
||||
rubocop-rspec
|
||||
scout_apm
|
||||
seed_dump
|
||||
sentry-rails (~> 5.3, >= 5.3.1)
|
||||
sentry-ruby (~> 5.3)
|
||||
sentry-sidekiq (~> 5.3, >= 5.3.1)
|
||||
sentry-rails
|
||||
sentry-ruby
|
||||
sentry-sidekiq
|
||||
shoulda-matchers
|
||||
sidekiq (~> 6.4.2)
|
||||
sidekiq-cron (~> 1.6, >= 1.6.0)
|
||||
|
||||
6
Procfile
6
Procfile
@@ -1,3 +1,3 @@
|
||||
release: bundle exec rails db:chatwoot_prepare
|
||||
web: bin/rails server -p $PORT -e $RAILS_ENV
|
||||
worker: bundle exec sidekiq -C config/sidekiq.yml
|
||||
release: POSTGRES_STATEMENT_TIMEOUT=600s bundle exec rails db:chatwoot_prepare
|
||||
web: bundle exec rails ip_lookup:setup && bin/rails server -p $PORT -e $RAILS_ENV
|
||||
worker: bundle exec rails ip_lookup:setup && bundle exec sidekiq -C config/sidekiq.yml
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
//= link administrate/application.css
|
||||
//= link administrate/application.js
|
||||
//= link dashboardChart.js
|
||||
//= link secretField.js
|
||||
|
||||
34
app/assets/javascripts/secretField.js
Normal file
34
app/assets/javascripts/secretField.js
Normal file
@@ -0,0 +1,34 @@
|
||||
// eslint-disable-next-line
|
||||
function toggleSecretField(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const toggler = e.currentTarget;
|
||||
const secretField = toggler.parentElement;
|
||||
const textElement = secretField.querySelector('[data-secret-masked]');
|
||||
|
||||
if (!textElement) return;
|
||||
|
||||
if (textElement.dataset.secretMasked === 'false') {
|
||||
textElement.textContent = '•'.repeat(10);
|
||||
textElement.dataset.secretMasked = 'true';
|
||||
toggler.querySelector('svg use').setAttribute('xlink:href', '#eye-show');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
textElement.textContent = secretField.dataset.secretText;
|
||||
textElement.dataset.secretMasked = 'false';
|
||||
toggler.querySelector('svg use').setAttribute('xlink:href', '#eye-hide');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line
|
||||
function copySecretField(e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
const toggler = e.currentTarget;
|
||||
const secretField = toggler.parentElement;
|
||||
|
||||
navigator.clipboard.writeText(secretField.dataset.secretText);
|
||||
}
|
||||
@@ -6,7 +6,6 @@
|
||||
@import 'utilities/text-color';
|
||||
|
||||
@import 'selectize';
|
||||
@import 'datetime_picker';
|
||||
|
||||
@import 'library/clearfix';
|
||||
@import 'library/data-label';
|
||||
|
||||
@@ -43,3 +43,20 @@
|
||||
.cell-label--number {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.cell-data__secret-field {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
|
||||
span {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: 5px;
|
||||
|
||||
svg {
|
||||
fill: currentColor;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,11 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
|
||||
result = {}
|
||||
# OAuthException, code: 100, error_subcode: 2018218, message: (#100) No profile available for this user
|
||||
# We don't need to capture this error as we don't care about contact params in case of echo messages
|
||||
ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception unless @outgoing_echo
|
||||
if e.message.include?('2018218')
|
||||
Rails.logger.warn e
|
||||
else
|
||||
ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception unless @outgoing_echo
|
||||
end
|
||||
rescue StandardError => e
|
||||
result = {}
|
||||
ChatwootExceptionTracker.new(e, account: @inbox.account).capture_exception
|
||||
|
||||
@@ -74,7 +74,7 @@ class V2::ReportBuilder
|
||||
:created_at,
|
||||
default_value: 0,
|
||||
range: range,
|
||||
permit: %w[day week month year],
|
||||
permit: %w[day week month year hour],
|
||||
time_zone: @timezone
|
||||
)
|
||||
end
|
||||
|
||||
@@ -55,9 +55,9 @@ class Api::V1::Accounts::ArticlesController < Api::V1::Accounts::BaseController
|
||||
|
||||
def article_params
|
||||
params.require(:article).permit(
|
||||
:title, :slug, :content, :description, :position, :category_id, :author_id, :associated_article_id, :status, meta: [:title,
|
||||
:description,
|
||||
{ tags: [] }]
|
||||
:title, :slug, :position, :content, :description, :position, :category_id, :author_id, :associated_article_id, :status, meta: [:title,
|
||||
:description,
|
||||
{ tags: [] }]
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
@@ -142,6 +142,8 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
||||
# and deprecate the support of passing only source_id as the param
|
||||
@contact_inbox ||= ::ContactInbox.find_by!(source_id: params[:source_id])
|
||||
authorize @contact_inbox.inbox, :show?
|
||||
rescue ActiveRecord::RecordNotUnique
|
||||
render json: { error: 'source_id should be unique' }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
def build_contact_inbox
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
class Api::V1::Accounts::Integrations::AppsController < Api::V1::Accounts::BaseController
|
||||
before_action :check_admin_authorization?
|
||||
before_action :check_admin_authorization?, except: [:index, :show]
|
||||
before_action :fetch_apps, only: [:index]
|
||||
before_action :fetch_app, only: [:show]
|
||||
|
||||
|
||||
28
app/controllers/api/v1/accounts/search_controller.rb
Normal file
28
app/controllers/api/v1/accounts/search_controller.rb
Normal file
@@ -0,0 +1,28 @@
|
||||
class Api::V1::Accounts::SearchController < Api::V1::Accounts::BaseController
|
||||
def index
|
||||
@result = search('all')
|
||||
end
|
||||
|
||||
def conversations
|
||||
@result = search('Conversation')
|
||||
end
|
||||
|
||||
def contacts
|
||||
@result = search('Contact')
|
||||
end
|
||||
|
||||
def messages
|
||||
@result = search('Message')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def search(search_type)
|
||||
SearchService.new(
|
||||
current_user: Current.user,
|
||||
current_account: Current.account,
|
||||
search_type: search_type,
|
||||
params: params
|
||||
).perform
|
||||
end
|
||||
end
|
||||
@@ -17,7 +17,7 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
|
||||
@message.update!(submitted_email: contact_email)
|
||||
ContactIdentifyAction.new(
|
||||
contact: @contact,
|
||||
params: { email: contact_email }
|
||||
params: { email: contact_email, name: contact_name }
|
||||
).perform
|
||||
else
|
||||
@message.update!(message_update_params[:message])
|
||||
|
||||
@@ -45,13 +45,18 @@ class Microsoft::CallbacksController < ApplicationController
|
||||
channel_email.inbox
|
||||
end
|
||||
|
||||
# Fallback name, for when name field is missing from users_data
|
||||
def fallback_name
|
||||
users_data['email'].split('@').first.parameterize.titleize
|
||||
end
|
||||
|
||||
def create_microsoft_channel_with_inbox
|
||||
ActiveRecord::Base.transaction do
|
||||
channel_email = Channel::Email.create!(email: users_data['email'], account: account)
|
||||
account.inboxes.create!(
|
||||
account: account,
|
||||
channel: channel_email,
|
||||
name: users_data['name']
|
||||
name: users_data['name'] || fallback_name
|
||||
)
|
||||
channel_email
|
||||
end
|
||||
|
||||
@@ -12,6 +12,8 @@ class Public::Api::V1::Inboxes::MessagesController < Public::Api::V1::InboxesCon
|
||||
end
|
||||
|
||||
def update
|
||||
render json: { error: 'You cannot update the CSAT survey after 14 days' }, status: :unprocessable_entity and return if check_csat_locked
|
||||
|
||||
@message.update!(message_update_params)
|
||||
rescue StandardError => e
|
||||
render json: { error: @contact.errors, message: e.message }.to_json, status: :internal_server_error
|
||||
@@ -43,7 +45,7 @@ class Public::Api::V1::Inboxes::MessagesController < Public::Api::V1::InboxesCon
|
||||
end
|
||||
|
||||
def message_update_params
|
||||
params.permit(submitted_values: [:name, :title, :value])
|
||||
params.permit(submitted_values: [:name, :title, :value, { csat_survey_response: [:feedback_message, :rating] }])
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
@@ -64,4 +66,8 @@ class Public::Api::V1::Inboxes::MessagesController < Public::Api::V1::InboxesCon
|
||||
message_type: :incoming
|
||||
}
|
||||
end
|
||||
|
||||
def check_csat_locked
|
||||
(Time.zone.now.to_date - @message.created_at.to_date).to_i > 14 and @message.content_type == 'input_csat'
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class Public::Api::V1::Portals::ArticlesController < PublicController
|
||||
class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::BaseController
|
||||
before_action :ensure_custom_domain_request, only: [:show, :index]
|
||||
before_action :portal
|
||||
before_action :set_category, except: [:index]
|
||||
@@ -8,6 +8,7 @@ class Public::Api::V1::Portals::ArticlesController < PublicController
|
||||
def index
|
||||
@articles = @portal.articles
|
||||
@articles = @articles.search(list_params) if list_params.present?
|
||||
@articles.order(position: :asc)
|
||||
end
|
||||
|
||||
def show; end
|
||||
@@ -15,21 +16,30 @@ class Public::Api::V1::Portals::ArticlesController < PublicController
|
||||
private
|
||||
|
||||
def set_article
|
||||
@article = @category.articles.find(params[:id])
|
||||
@article = @category.articles.find(permitted_params[:id])
|
||||
@article.increment_view_count
|
||||
@parsed_content = render_article_content(@article.content)
|
||||
end
|
||||
|
||||
def set_category
|
||||
@category = @portal.categories.find_by!(slug: params[:category_slug]) if params[:category_slug].present?
|
||||
return if permitted_params[:category_slug].blank?
|
||||
|
||||
@category = @portal.categories.find_by!(
|
||||
slug: permitted_params[:category_slug],
|
||||
locale: permitted_params[:locale]
|
||||
)
|
||||
end
|
||||
|
||||
def portal
|
||||
@portal ||= Portal.find_by!(slug: params[:slug], archived: false)
|
||||
@portal ||= Portal.find_by!(slug: permitted_params[:slug], archived: false)
|
||||
end
|
||||
|
||||
def list_params
|
||||
params.permit(:query)
|
||||
params.permit(:query, :locale)
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:slug, :category_slug, :locale, :id)
|
||||
end
|
||||
|
||||
def render_article_content(content)
|
||||
|
||||
22
app/controllers/public/api/v1/portals/base_controller.rb
Normal file
22
app/controllers/public/api/v1/portals/base_controller.rb
Normal file
@@ -0,0 +1,22 @@
|
||||
class Public::Api::V1::Portals::BaseController < PublicController
|
||||
around_action :set_locale
|
||||
|
||||
private
|
||||
|
||||
def set_locale(&)
|
||||
switch_locale_with_portal(&) if params[:locale].present?
|
||||
end
|
||||
|
||||
def switch_locale_with_portal(&)
|
||||
locale_without_variant = params[:locale].split('_')[0]
|
||||
is_locale_available = I18n.available_locales.map(&:to_s).include?(params[:locale])
|
||||
is_locale_variant_available = I18n.available_locales.map(&:to_s).include?(locale_without_variant)
|
||||
if is_locale_available
|
||||
@locale = params[:locale]
|
||||
elsif is_locale_variant_available
|
||||
@locale = locale_without_variant
|
||||
end
|
||||
|
||||
I18n.with_locale(@locale, &)
|
||||
end
|
||||
end
|
||||
@@ -1,11 +1,11 @@
|
||||
class Public::Api::V1::Portals::CategoriesController < PublicController
|
||||
class Public::Api::V1::Portals::CategoriesController < Public::Api::V1::Portals::BaseController
|
||||
before_action :ensure_custom_domain_request, only: [:show, :index]
|
||||
before_action :portal
|
||||
before_action :set_category, only: [:show]
|
||||
layout 'portal'
|
||||
|
||||
def index
|
||||
@categories = @portal.categories
|
||||
@categories = @portal.categories.order(position: :asc)
|
||||
end
|
||||
|
||||
def show; end
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
class Public::Api::V1::PortalsController < PublicController
|
||||
class Public::Api::V1::PortalsController < Public::Api::V1::Portals::BaseController
|
||||
before_action :ensure_custom_domain_request, only: [:show]
|
||||
before_action :portal
|
||||
before_action :redirect_to_portal_with_locale, only: [:show]
|
||||
|
||||
@@ -5,6 +5,7 @@ class WidgetsController < ActionController::Base
|
||||
before_action :set_global_config
|
||||
before_action :set_web_widget
|
||||
before_action :ensure_account_is_active
|
||||
before_action :ensure_location_is_supported
|
||||
before_action :set_token
|
||||
before_action :set_contact
|
||||
before_action :build_contact
|
||||
@@ -18,6 +19,9 @@ class WidgetsController < ActionController::Base
|
||||
|
||||
def set_web_widget
|
||||
@web_widget = ::Channel::WebWidget.find_by!(website_token: permitted_params[:website_token])
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
Rails.logger.error('web widget does not exist')
|
||||
render json: { error: 'web widget does not exist' }, status: :not_found
|
||||
end
|
||||
|
||||
def set_token
|
||||
@@ -51,6 +55,8 @@ class WidgetsController < ActionController::Base
|
||||
render json: { error: 'Account is suspended' }, status: :unauthorized unless @web_widget.inbox.account.active?
|
||||
end
|
||||
|
||||
def ensure_location_is_supported; end
|
||||
|
||||
def additional_attributes
|
||||
if @web_widget.inbox.account.feature_enabled?('ip_lookup')
|
||||
{ created_at_ip: request.remote_ip }
|
||||
@@ -67,3 +73,5 @@ class WidgetsController < ActionController::Base
|
||||
response.headers.delete('X-Frame-Options')
|
||||
end
|
||||
end
|
||||
|
||||
WidgetsController.prepend_mod_with('WidgetsController')
|
||||
|
||||
@@ -10,7 +10,7 @@ class AccessTokenDashboard < Administrate::BaseDashboard
|
||||
ATTRIBUTE_TYPES = {
|
||||
owner: Field::Polymorphic,
|
||||
id: Field::Number,
|
||||
token: Field::String,
|
||||
token: SecretField,
|
||||
created_at: Field::DateTime,
|
||||
updated_at: Field::DateTime
|
||||
}.freeze
|
||||
|
||||
4
app/fields/secret_field.rb
Normal file
4
app/fields/secret_field.rb
Normal file
@@ -0,0 +1,4 @@
|
||||
require 'administrate/field/base'
|
||||
|
||||
class SecretField < Administrate::Field::String
|
||||
end
|
||||
@@ -1,4 +1,6 @@
|
||||
class EmailChannelFinder
|
||||
include EmailHelper
|
||||
|
||||
def initialize(email_object)
|
||||
@email_object = email_object
|
||||
end
|
||||
@@ -7,7 +9,8 @@ class EmailChannelFinder
|
||||
channel = nil
|
||||
recipient_mails = @email_object.to.to_a + @email_object.cc.to_a
|
||||
recipient_mails.each do |email|
|
||||
channel = Channel::Email.find_by('lower(email) = ? OR lower(forward_to_email) = ?', email.downcase, email.downcase)
|
||||
normalized_email = normalize_email_with_plus_addressing(email)
|
||||
channel = Channel::Email.find_by('lower(email) = ? OR lower(forward_to_email) = ?', normalized_email, normalized_email)
|
||||
break if channel.present?
|
||||
end
|
||||
channel
|
||||
|
||||
@@ -3,4 +3,11 @@ module EmailHelper
|
||||
domain = email.split('@').last
|
||||
domain.split('.').first
|
||||
end
|
||||
|
||||
# ref: https://www.rfc-editor.org/rfc/rfc5233.html
|
||||
# This is not a mandatory requirement for email addresses, but it is a common practice.
|
||||
# john+test@xyc.com is the same as john@xyc.com
|
||||
def normalize_email_with_plus_addressing(email)
|
||||
"#{email.split('@').first.split('+').first}@#{email.split('@').last}".downcase
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,6 +17,15 @@ module ReportingEventHelper
|
||||
from_in_inbox_timezone.working_time_until(to_in_inbox_timezone)
|
||||
end
|
||||
|
||||
def last_non_human_activity(conversation)
|
||||
# check if a handoff event already exists
|
||||
handoff_event = ReportingEvent.where(conversation_id: conversation.id, name: 'conversation_bot_handoff').last
|
||||
|
||||
# if a handoff exists, last non human activity is when the handoff ended,
|
||||
# otherwise it's when the conversation was created
|
||||
handoff_event&.event_end_time || conversation.created_at
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def configure_working_hours(working_hours)
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
<template>
|
||||
<div v-if="!authUIFlags.isFetching" id="app" class="app-wrapper app-root">
|
||||
<div
|
||||
v-if="!authUIFlags.isFetching"
|
||||
id="app"
|
||||
class="app-wrapper app-root"
|
||||
:class="{ 'app-rtl--wrapper': isRTLView }"
|
||||
>
|
||||
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
|
||||
<transition name="fade" mode="out-in">
|
||||
<router-view />
|
||||
@@ -22,6 +27,7 @@ import NetworkNotification from './components/NetworkNotification';
|
||||
import UpdateBanner from './components/app/UpdateBanner.vue';
|
||||
import vueActionCable from './helper/actionCable';
|
||||
import WootSnackbarBox from './components/SnackbarContainer';
|
||||
import rtlMixin from 'shared/mixins/rtlMixin';
|
||||
import {
|
||||
registerSubscription,
|
||||
verifyServiceWorkerExistence,
|
||||
@@ -38,6 +44,8 @@ export default {
|
||||
WootSnackbarBox,
|
||||
},
|
||||
|
||||
mixins: [rtlMixin],
|
||||
|
||||
data() {
|
||||
return {
|
||||
showAddAccountModal: false,
|
||||
@@ -96,6 +104,7 @@ export default {
|
||||
} = this.getAccount(this.currentAccountId);
|
||||
const { pubsub_token: pubsubToken } = this.currentUser || {};
|
||||
this.setLocale(locale);
|
||||
this.updateRTLDirectionView(locale);
|
||||
this.latestChatwootVersion = latestChatwootVersion;
|
||||
vueActionCable.init(pubsubToken);
|
||||
},
|
||||
|
||||
@@ -8,20 +8,20 @@ class ReportsAPI extends ApiClient {
|
||||
super('reports', { accountScoped: true, apiVersion: 'v2' });
|
||||
}
|
||||
|
||||
getReports(
|
||||
getReports({
|
||||
metric,
|
||||
since,
|
||||
until,
|
||||
from,
|
||||
to,
|
||||
type = 'account',
|
||||
id,
|
||||
group_by,
|
||||
business_hours
|
||||
) {
|
||||
business_hours,
|
||||
}) {
|
||||
return axios.get(`${this.url}`, {
|
||||
params: {
|
||||
metric,
|
||||
since,
|
||||
until,
|
||||
since: from,
|
||||
until: to,
|
||||
type,
|
||||
id,
|
||||
group_by,
|
||||
|
||||
18
app/javascript/dashboard/api/search.js
Normal file
18
app/javascript/dashboard/api/search.js
Normal file
@@ -0,0 +1,18 @@
|
||||
/* global axios */
|
||||
import ApiClient from './ApiClient';
|
||||
|
||||
class SearchAPI extends ApiClient {
|
||||
constructor() {
|
||||
super('search', { accountScoped: true });
|
||||
}
|
||||
|
||||
get({ q }) {
|
||||
return axios.get(this.url, {
|
||||
params: {
|
||||
q,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new SearchAPI();
|
||||
@@ -20,7 +20,11 @@ describe('#Reports API', () => {
|
||||
});
|
||||
describeWithAPIMock('API calls', context => {
|
||||
it('#getAccountReports', () => {
|
||||
reportsAPI.getReports('conversations_count', 1621103400, 1621621800);
|
||||
reportsAPI.getReports({
|
||||
metric: 'conversations_count',
|
||||
from: 1621103400,
|
||||
to: 1621621800,
|
||||
});
|
||||
expect(context.axiosMock.get).toHaveBeenCalledWith('/api/v2/reports', {
|
||||
params: {
|
||||
metric: 'conversations_count',
|
||||
|
||||
@@ -14,11 +14,11 @@
|
||||
}
|
||||
|
||||
.mx-input {
|
||||
border: 1px solid var(--color-border);
|
||||
border: 1px solid var(--s-200);
|
||||
border-radius: var(--border-radius-normal);
|
||||
box-shadow: none;
|
||||
display: flex;
|
||||
height: 4.6rem;
|
||||
height: 4.0rem;
|
||||
}
|
||||
|
||||
.mx-input:disabled,
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
height: 4.0rem;
|
||||
}
|
||||
|
||||
.card {
|
||||
margin-bottom: var(--space-small);
|
||||
padding: var(--space-normal);
|
||||
@@ -64,7 +68,3 @@ code {
|
||||
.padding-right-small {
|
||||
padding-right: var(--space-one);
|
||||
}
|
||||
|
||||
.margin-right-small {
|
||||
margin-right: var(--space-small);
|
||||
}
|
||||
|
||||
@@ -16,12 +16,12 @@ body {
|
||||
}
|
||||
|
||||
.banner + .app-wrapper {
|
||||
.button--fixed-right-top {
|
||||
.button--fixed-top {
|
||||
top: 5.6 * $space-one;
|
||||
}
|
||||
|
||||
.off-canvas-content {
|
||||
.button--fixed-right-top {
|
||||
.button--fixed-top {
|
||||
top: $space-small;
|
||||
}
|
||||
}
|
||||
|
||||
499
app/javascript/dashboard/assets/scss/_rtl.scss
Normal file
499
app/javascript/dashboard/assets/scss/_rtl.scss
Normal file
@@ -0,0 +1,499 @@
|
||||
.app-rtl--wrapper {
|
||||
direction: rtl;
|
||||
|
||||
// Primary sidebar
|
||||
.primary--sidebar {
|
||||
border-left: 1px solid var(--s-50);
|
||||
border-right: 0;
|
||||
|
||||
.options-menu.dropdown-pane {
|
||||
right: var(--space-smaller);
|
||||
|
||||
.auto-offline--toggle {
|
||||
padding: var(--space-smaller) var(--space-one) var(--space-smaller)
|
||||
var(--space-smaller);
|
||||
}
|
||||
|
||||
.status-items .button {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Secondary sidebar
|
||||
.secondary-sidebar {
|
||||
.secondary-menu {
|
||||
border-left: 1px solid var(--s-50);
|
||||
border-right: 0;
|
||||
|
||||
.nested.vertical.menu {
|
||||
.badge--icon {
|
||||
margin-left: var(--space-smaller);
|
||||
margin-right: unset;
|
||||
}
|
||||
|
||||
.menu-label {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.secondary-menu--icon {
|
||||
margin-left: var(--space-smaller);
|
||||
margin-right: unset;
|
||||
}
|
||||
|
||||
.account-context--group .account-context--switch-group {
|
||||
--overlay-shadow: linear-gradient(
|
||||
to left,
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, 1) 50%
|
||||
);
|
||||
background-image: var(--overlay-shadow);
|
||||
}
|
||||
|
||||
// Help center sidebar
|
||||
.sidebar-header--wrap .header-title--wrap {
|
||||
margin-left: unset;
|
||||
margin-right: var(--space-small);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Woot button
|
||||
.button {
|
||||
.icon--emoji + .button__content {
|
||||
padding-left: 0;
|
||||
padding-right: var(--space-small);
|
||||
}
|
||||
|
||||
.icon--font + .button__content {
|
||||
padding-left: 0;
|
||||
padding-right: var(--space-small);
|
||||
}
|
||||
|
||||
.icon + .button__content {
|
||||
padding-left: 0;
|
||||
padding-right: var(--space-small);
|
||||
}
|
||||
}
|
||||
|
||||
// Settings header
|
||||
.settings-header {
|
||||
.header--icon {
|
||||
margin-left: var(--space-small);
|
||||
margin-right: var(--space-smaller);
|
||||
}
|
||||
}
|
||||
|
||||
.settings.back-button {
|
||||
direction: initial;
|
||||
margin-left: var(--space-normal);
|
||||
margin-right: var(--space-smaller);
|
||||
}
|
||||
|
||||
// Settings header action button
|
||||
.button--fixed-top {
|
||||
left: $space-small;
|
||||
position: fixed;
|
||||
right: unset;
|
||||
top: $space-small;
|
||||
}
|
||||
|
||||
// Woot Tabs
|
||||
.tabs-title {
|
||||
&:first-child {
|
||||
margin-left: var(--space-small);
|
||||
margin-right: unset;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
margin-left: unset;
|
||||
margin-right: var(--space-small);
|
||||
}
|
||||
}
|
||||
|
||||
// woot tables
|
||||
table,
|
||||
thead,
|
||||
th {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
// Table footer
|
||||
.footer {
|
||||
.page-meta {
|
||||
direction: initial;
|
||||
}
|
||||
}
|
||||
|
||||
// Wizard box
|
||||
.wizard-box {
|
||||
direction: initial;
|
||||
}
|
||||
|
||||
// Conversation details
|
||||
.conversation-details-wrap {
|
||||
.conv-header {
|
||||
.user {
|
||||
margin-left: var(--space-normal);
|
||||
margin-right: unset;
|
||||
|
||||
.user--profile__meta {
|
||||
margin-left: unset;
|
||||
margin-right: var(--space-small);
|
||||
}
|
||||
}
|
||||
|
||||
.actions--container .resolve-actions {
|
||||
margin-left: unset;
|
||||
margin-right: var(--space-small);
|
||||
}
|
||||
}
|
||||
|
||||
.conversation-panel {
|
||||
// Message text
|
||||
.text-content {
|
||||
p {
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
|
||||
ul {
|
||||
padding-left: unset;
|
||||
padding-right: var(--space-two);
|
||||
}
|
||||
|
||||
li {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
// Message items and actions
|
||||
li {
|
||||
&.right {
|
||||
.sender--info {
|
||||
padding: var(--space-small) var(--space-smaller)
|
||||
var(--space-smaller) 0;
|
||||
}
|
||||
|
||||
.context-menu-wrap {
|
||||
margin-left: 0;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Conversation footer
|
||||
.conversation-footer {
|
||||
.preview-item {
|
||||
direction: initial;
|
||||
}
|
||||
}
|
||||
|
||||
// Custom attributes section in conversation sidebar
|
||||
.conversation-sidebar-wrap .checkbox-wrap {
|
||||
.checkbox {
|
||||
margin-left: var(--space-small);
|
||||
}
|
||||
}
|
||||
|
||||
// Conversation sidebar toggle button
|
||||
.sidebar-toggle--button {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
// Conversation sidebar close button
|
||||
.close-button--rtl {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
// Resolve actions button
|
||||
.resolve-actions {
|
||||
.button-group .button:first-child {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: var(--border-radius-normal);
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: var(--border-radius-normal);
|
||||
}
|
||||
|
||||
.button-group .button:last-child {
|
||||
border-bottom-left-radius: var(--border-radius-normal);
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-left-radius: var(--border-radius-normal);
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Conversation list
|
||||
.conversations-list-wrap {
|
||||
border-right: 0;
|
||||
|
||||
.conversation {
|
||||
.conversation--meta {
|
||||
left: $space-normal;
|
||||
right: unset;
|
||||
|
||||
.unread {
|
||||
margin-left: unset;
|
||||
margin-right: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.assignee-label {
|
||||
margin-left: 0;
|
||||
margin-right: var(--space-one);
|
||||
}
|
||||
|
||||
.show-more--button {
|
||||
margin: unset;
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.search-header--wrap {
|
||||
.search--input {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.layout-switch__container {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
// Card label
|
||||
.label-container {
|
||||
.label {
|
||||
margin-left: var(--space-smaller);
|
||||
margin-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Secondary sidebar toggle button
|
||||
.toggle-sidebar {
|
||||
margin-left: 0;
|
||||
margin-right: var(--space-minus-small);
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
// Bulk actions
|
||||
.bulk-action__container {
|
||||
.triangle {
|
||||
left: var(--triangle-position);
|
||||
right: unset;
|
||||
}
|
||||
|
||||
.bulk-action__agents {
|
||||
left: var(--space-small);
|
||||
right: unset;
|
||||
}
|
||||
|
||||
.labels-container {
|
||||
left: var(--space-small);
|
||||
right: unset;
|
||||
|
||||
.label-checkbox {
|
||||
margin: 0 0 0 var(--space-one);
|
||||
}
|
||||
}
|
||||
|
||||
.actions-container {
|
||||
left: var(--space-small);
|
||||
right: unset;
|
||||
}
|
||||
|
||||
.bulk-action__teams {
|
||||
left: var(--space-small);
|
||||
right: unset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Contact notes
|
||||
.card.note-wrap {
|
||||
.time-stamp {
|
||||
unicode-bidi: plaintext;
|
||||
}
|
||||
}
|
||||
|
||||
// Notification panel
|
||||
.notification-wrap {
|
||||
left: 0;
|
||||
right: var(--space-jumbo);
|
||||
|
||||
.action-button {
|
||||
margin-left: var(--space-small);
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.notification-content--wrap {
|
||||
margin-left: 0;
|
||||
margin-right: var(--space-small);
|
||||
}
|
||||
}
|
||||
|
||||
// Help center
|
||||
.article-container .row--article-block {
|
||||
td:last-child {
|
||||
direction: initial;
|
||||
}
|
||||
}
|
||||
|
||||
// scss-lint:disable SelectorDepth
|
||||
.container .header-wrap .header-left-wrap .header-left-wrap > .page-title {
|
||||
margin-right: var(--space-small);
|
||||
}
|
||||
|
||||
.portal-container .container {
|
||||
margin-left: unset !important;
|
||||
margin-right: var(--space-small);
|
||||
|
||||
.configuration-items--wrap {
|
||||
margin-left: var(--space-mega);
|
||||
margin-right: unset !important;
|
||||
}
|
||||
|
||||
thead th {
|
||||
padding-left: var(--space-small);
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
tbody td {
|
||||
padding-left: var(--space-small);
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.portal-popover__container .portal {
|
||||
.actions-container {
|
||||
margin-left: unset;
|
||||
margin-right: var(--space-one);
|
||||
}
|
||||
}
|
||||
|
||||
.edit-article--container {
|
||||
.header-right--wrap {
|
||||
.button-group .button:first-child {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: var(--border-radius-normal);
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: var(--border-radius-normal);
|
||||
}
|
||||
|
||||
.button-group .button:last-child {
|
||||
border-bottom-left-radius: var(--border-radius-normal);
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-left-radius: var(--border-radius-normal);
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.header-left--wrap {
|
||||
.back-button {
|
||||
direction: initial;
|
||||
}
|
||||
}
|
||||
|
||||
.article--buttons {
|
||||
.dropdown-pane {
|
||||
left: 0;
|
||||
position: absolute;
|
||||
right: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar-button {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
|
||||
.article-settings--container {
|
||||
border-left: 0;
|
||||
border-right: 1px solid var(--color-border-light);
|
||||
flex-direction: row-reverse;
|
||||
margin-left: 0;
|
||||
margin-right: var(--space-normal);
|
||||
padding-left: 0;
|
||||
padding-right: var(--space-normal);
|
||||
}
|
||||
|
||||
.category-list--container .header-left--wrap {
|
||||
direction: initial;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
// Toggle switch
|
||||
.toggle-button {
|
||||
&.small {
|
||||
span {
|
||||
&.active {
|
||||
transform: translate(var(--space-minus-small), var(--space-zero));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
span {
|
||||
--minus-space-one-point-five: -1.5rem;
|
||||
|
||||
&.active {
|
||||
transform: translate(
|
||||
var(--minus-space-one-point-five),
|
||||
var(--space-zero)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Widget builder
|
||||
.widget-builder-container .widget-preview {
|
||||
direction: initial;
|
||||
}
|
||||
|
||||
// Modal
|
||||
.modal-container {
|
||||
text-align: right;
|
||||
|
||||
.modal-footer {
|
||||
button {
|
||||
margin-left: 0;
|
||||
margin-right: var(--space-small);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Other changes
|
||||
|
||||
.account-selector--wrap {
|
||||
direction: initial;
|
||||
}
|
||||
|
||||
.inbox--name .inbox--icon {
|
||||
margin-left: var(--space-micro);
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.colorpicker--chrome {
|
||||
direction: initial;
|
||||
}
|
||||
|
||||
.mention--box {
|
||||
direction: initial;
|
||||
}
|
||||
|
||||
.contact--details .contact--bio {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.merge-contacts .child-contact-wrap {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.contact--form .input-group {
|
||||
direction: initial;
|
||||
}
|
||||
|
||||
// scss-lint:disable QualifyingElement
|
||||
.dropdown-menu--header > span.title {
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,3 @@
|
||||
.margin-right-small {
|
||||
margin-right: var(--space-small);
|
||||
}
|
||||
|
||||
.margin-bottom-small {
|
||||
margin-bottom: var(--space-small);
|
||||
}
|
||||
@@ -14,6 +10,10 @@
|
||||
margin-left: var(--space-minus-slab);
|
||||
}
|
||||
|
||||
.margin-right-minus-slab {
|
||||
margin-right: var(--space-minus-slab);
|
||||
}
|
||||
|
||||
.fs-small {
|
||||
font-size: var(--font-size-small);
|
||||
}
|
||||
@@ -75,3 +75,9 @@
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.button--fixed-top {
|
||||
position: fixed;
|
||||
right: var(--space-small);
|
||||
top: var(--space-small);
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@
|
||||
@import 'layout';
|
||||
@import 'animations';
|
||||
@import 'foundation-custom';
|
||||
@import 'rtl';
|
||||
|
||||
@import 'widgets/buttons';
|
||||
@import 'widgets/conv-header';
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
width: 100%;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
p {
|
||||
@@ -169,7 +169,7 @@
|
||||
}
|
||||
|
||||
.multiselect-wrap--small {
|
||||
$multiselect-height: 3.8rem;
|
||||
$multiselect-height: 4.0rem;
|
||||
|
||||
.multiselect__tags,
|
||||
.multiselect__input {
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
.integration--image {
|
||||
display: flex;
|
||||
height: 10rem;
|
||||
margin-right: $space-normal;
|
||||
width: 10rem;
|
||||
|
||||
img {
|
||||
@@ -18,12 +17,15 @@
|
||||
}
|
||||
}
|
||||
|
||||
.integration--title {
|
||||
font-size: $font-size-large;
|
||||
.integration--type {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
margin: 0 var(--space-normal);
|
||||
}
|
||||
|
||||
.integration--description {
|
||||
padding-right: $space-medium;
|
||||
.integration--title {
|
||||
font-size: var(--font-size-large);
|
||||
}
|
||||
|
||||
.button-wrap {
|
||||
|
||||
@@ -50,22 +50,22 @@ $default-button-height: 4.0rem;
|
||||
|
||||
&.secondary {
|
||||
border-color: var(--s-200);
|
||||
color: var(--s-700)
|
||||
color: var(--s-700);
|
||||
}
|
||||
|
||||
&.success {
|
||||
border-color: var(--s-200);
|
||||
color: var(--g-700)
|
||||
color: var(--g-700);
|
||||
}
|
||||
|
||||
&.alert {
|
||||
border-color: var(--s-200);
|
||||
color: var(--r-700)
|
||||
color: var(--r-700);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
border-color: var(--s-200);
|
||||
color: var(--y-700)
|
||||
color: var(--y-700);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@@ -116,19 +116,19 @@ $default-button-height: 4.0rem;
|
||||
color: var(--w-700);
|
||||
|
||||
&.secondary {
|
||||
color: var(--s-700)
|
||||
color: var(--s-700);
|
||||
}
|
||||
|
||||
&.success {
|
||||
color: var(--g-700)
|
||||
color: var(--g-700);
|
||||
}
|
||||
|
||||
&.alert {
|
||||
color: var(--r-700)
|
||||
color: var(--r-700);
|
||||
}
|
||||
|
||||
&.warning {
|
||||
color: var(--y-700)
|
||||
color: var(--y-700);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@@ -205,11 +205,3 @@ $default-button-height: 4.0rem;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// @TDOD move to utility file
|
||||
.button--fixed-right-top {
|
||||
position: fixed;
|
||||
right: $space-small;
|
||||
top: $space-small;
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ $resolve-button-width: 13.2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
margin-left: $space-slab;
|
||||
margin-left: var(--space-small);
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,18 +13,18 @@
|
||||
@include flex;
|
||||
@include flex-shrink;
|
||||
border-bottom: 1px solid transparent;
|
||||
border-left: $space-micro solid transparent;
|
||||
border-left: var(--space-micro) solid transparent;
|
||||
border-top: 1px solid transparent;
|
||||
cursor: pointer;
|
||||
padding: 0 0 0 $space-normal;
|
||||
padding: 0 var(--space-normal);
|
||||
position: relative;
|
||||
|
||||
&.active {
|
||||
animation: left-shift-animation 0.25s $swift-ease-out-function;
|
||||
background: $color-background;
|
||||
border-bottom-color: $color-border-light;
|
||||
border-left-color: $color-woot;
|
||||
border-top-color: $color-border-light;
|
||||
background: var(--color-background);
|
||||
border-bottom-color: var(--color-border-light);
|
||||
border-left-color: var(--color-woot);
|
||||
border-top-color: var(--color-border-light);
|
||||
|
||||
.conversation--details {
|
||||
border-top-color: transparent;
|
||||
@@ -43,7 +43,7 @@
|
||||
|
||||
&:last-child {
|
||||
.conversation--details {
|
||||
border-bottom-color: $color-border-light;
|
||||
border-bottom-color: var(--color-border-light);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,33 +51,32 @@
|
||||
@include border-light-bottom;
|
||||
@include border-light-top;
|
||||
border-bottom-color: transparent;
|
||||
margin: 0 0 0 $space-one;
|
||||
padding: $space-slab 0;
|
||||
padding: var(--space-slab) 0;
|
||||
}
|
||||
|
||||
.conversation--user {
|
||||
font-size: $font-size-small;
|
||||
margin-bottom: 0;
|
||||
font-size: var(--font-size-small);
|
||||
margin: 0 var(--space-small);
|
||||
text-transform: capitalize;
|
||||
|
||||
.label {
|
||||
left: $space-micro;
|
||||
max-width: $space-jumbo;
|
||||
left: var(--space-one);
|
||||
max-width: var(--space-jumbo);
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
text-overflow: ellipsis;
|
||||
top: $space-micro;
|
||||
top: var(--space-one);
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.conversation--message {
|
||||
color: $color-body;
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-normal;
|
||||
height: $space-medium;
|
||||
line-height: $space-medium;
|
||||
margin: 0;
|
||||
color: var(--color-body);
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-normal);
|
||||
height: var(--space-medium);
|
||||
line-height: var(--space-medium);
|
||||
margin: 0 var(--space-small);
|
||||
max-width: 96%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -89,32 +88,31 @@
|
||||
@include flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
right: $space-normal;
|
||||
top: $space-normal;
|
||||
right: var(--space-normal);
|
||||
top: var(--space-normal);
|
||||
|
||||
.unread {
|
||||
$unread-size: $space-normal;
|
||||
@include round-corner;
|
||||
@include light-shadow;
|
||||
background: darken($success-color, 3%);
|
||||
color: $color-white;
|
||||
color: var(--white);
|
||||
display: none;
|
||||
font-size: $font-size-micro;
|
||||
font-weight: $font-weight-black;
|
||||
height: $unread-size;
|
||||
line-height: $unread-size;
|
||||
font-size: var(--font-size-micro);
|
||||
font-weight: var(--font-weight-black);
|
||||
height: var(--space-normal);
|
||||
line-height: var(--space-normal);
|
||||
margin-left: auto;
|
||||
margin-top: $space-smaller;
|
||||
min-width: $unread-size;
|
||||
padding: 0 $space-smaller;
|
||||
margin-top: var(--space-smaller);
|
||||
min-width: var(--space-normal);
|
||||
padding: 0 var(--space-smaller);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.timestamp {
|
||||
color: $dark-gray;
|
||||
font-size: $font-size-micro;
|
||||
font-weight: $font-weight-normal;
|
||||
line-height: $space-normal;
|
||||
font-size: var(--font-size-micro);
|
||||
font-weight: var(--font-weight-normal);
|
||||
line-height: var(--space-normal);
|
||||
margin-left: auto;
|
||||
}
|
||||
}
|
||||
@@ -125,11 +123,11 @@
|
||||
}
|
||||
|
||||
.conversation--message {
|
||||
font-weight: $font-weight-bold;
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
.conversation--user {
|
||||
font-weight: $font-weight-bold;
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
|
||||
.conversations-list-wrap {
|
||||
@include flex;
|
||||
border-right: 1px solid var(--s-50);
|
||||
flex-direction: column;
|
||||
|
||||
.load-more-conversations {
|
||||
@@ -81,8 +82,8 @@
|
||||
}
|
||||
|
||||
.conversations-list {
|
||||
overflow-y: auto;
|
||||
flex: 1 1;
|
||||
overflow-y: auto;
|
||||
|
||||
@include breakpoint(large up) {
|
||||
@include scroll-on-hover;
|
||||
@@ -93,22 +94,10 @@
|
||||
@include flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: $zero $zero $space-micro;
|
||||
padding: 0 var(--space-normal);
|
||||
|
||||
.page-title {
|
||||
margin-bottom: $zero;
|
||||
margin-left: $space-normal;
|
||||
}
|
||||
|
||||
.status--filter {
|
||||
background-color: $color-background-light;
|
||||
border: 1px solid $color-border;
|
||||
float: right;
|
||||
font-size: $font-size-mini;
|
||||
height: $space-medium;
|
||||
margin: 0;
|
||||
padding: $zero $space-medium $zero $space-normal;
|
||||
width: auto;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -391,14 +380,6 @@
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left-color: var(--s-300);
|
||||
|
||||
p {
|
||||
color: var(--s-300);
|
||||
}
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -419,19 +400,8 @@
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left-color: var(--w-100);
|
||||
|
||||
p {
|
||||
color: var(--w-100);
|
||||
}
|
||||
}
|
||||
|
||||
pre code {
|
||||
background: var(--color-background);
|
||||
}
|
||||
|
||||
p:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,23 +3,23 @@
|
||||
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
padding: $space-normal $space-small $space-normal $space-two;
|
||||
padding: var(--space-normal);
|
||||
|
||||
&.active {
|
||||
@include custom-border-top(3px, $color-woot);
|
||||
@include custom-border-top(3px, var(--color-woot));
|
||||
@include background-white;
|
||||
.heading,
|
||||
.metric {
|
||||
color: $color-woot;
|
||||
color: var(--color-woot);
|
||||
}
|
||||
}
|
||||
|
||||
.heading {
|
||||
align-items: center;
|
||||
color: $color-heading;
|
||||
color: var(--color-heading);
|
||||
display: flex;
|
||||
font-size: $font-size-small;
|
||||
font-weight: $font-weight-bold;
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
@@ -29,19 +29,19 @@
|
||||
}
|
||||
|
||||
.metric-wrap {
|
||||
align-items: baseline;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.metric {
|
||||
font-size: $font-size-big;
|
||||
font-weight: $font-weight-feather;
|
||||
margin-top: $space-smaller;
|
||||
font-size: var(--font-size-big);
|
||||
font-weight: var(--font-weight-feather);
|
||||
margin-top: var(--space-smaller);
|
||||
}
|
||||
|
||||
.metric-trend {
|
||||
font-size: $font-size-small;
|
||||
margin-left: $space-small;
|
||||
font-size: var(--font-size-small);
|
||||
margin: 0 var(--space-small);
|
||||
}
|
||||
|
||||
.metric-up {
|
||||
@@ -53,7 +53,7 @@
|
||||
}
|
||||
|
||||
.desc {
|
||||
font-size: $font-size-small;
|
||||
font-size: var(--font-size-small);
|
||||
margin: 0;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
@@ -62,8 +62,8 @@
|
||||
.report-bar {
|
||||
@include background-white;
|
||||
@include border-light;
|
||||
margin: -1px $zero;
|
||||
padding: $space-small $space-medium;
|
||||
margin: var(--space-minus-micro) 0;
|
||||
padding: var(--space-small) var(--space-medium);
|
||||
|
||||
.chart-container {
|
||||
@include flex;
|
||||
@@ -76,12 +76,12 @@
|
||||
|
||||
.empty-state {
|
||||
color: $color-gray;
|
||||
font-size: $font-size-default;
|
||||
margin: $space-jumbo;
|
||||
font-size: var(--font-size-default);
|
||||
margin: var(--space-jumbo);
|
||||
}
|
||||
|
||||
.business-hours {
|
||||
margin: $space-normal;
|
||||
margin: var(--space-normal);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,11 +1,3 @@
|
||||
.date-picker {
|
||||
margin-left: var(--space-smaller);
|
||||
}
|
||||
|
||||
.margin-left-small {
|
||||
margin-left: var(--space-smaller);
|
||||
}
|
||||
|
||||
.reports-option__rounded--item {
|
||||
border-radius: 100%;
|
||||
height: var(--space-two);
|
||||
@@ -26,17 +18,21 @@
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.reports-option__title {
|
||||
margin: 0 var(--space-small);
|
||||
}
|
||||
|
||||
.business-hours {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: end;
|
||||
margin-bottom: var(--space-normal);
|
||||
justify-content: flex-start;
|
||||
margin-left: auto;
|
||||
padding-right: var(--space-normal);
|
||||
}
|
||||
|
||||
.business-hours-text {
|
||||
font-size: var(--font-size-small);
|
||||
margin: 0 var(--space-small);
|
||||
}
|
||||
|
||||
.switch {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
.side-menu {
|
||||
i {
|
||||
margin-right: $space-smaller;
|
||||
min-width: $space-two;
|
||||
margin-right: var(--space-smaller);
|
||||
min-width: var(--space-two);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,14 +18,14 @@
|
||||
|
||||
.nested {
|
||||
a {
|
||||
font-size: $font-size-small;
|
||||
margin-bottom: $space-micro;
|
||||
margin-top: $space-micro;
|
||||
font-size: var(--font-size-small);
|
||||
margin-bottom: var(--space-micro);
|
||||
margin-top: var(--space-micro);
|
||||
|
||||
.inbox-icon {
|
||||
display: inline-block;
|
||||
margin-right: $space-micro;
|
||||
min-width: $space-normal;
|
||||
margin-right: var(--space-micro);
|
||||
min-width: var(--space-normal);
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,8 @@
|
||||
@include space-between-column;
|
||||
@include border-normal-top;
|
||||
flex-direction: column;
|
||||
padding: $space-one $space-normal $space-one $space-one;
|
||||
padding: var(--space-one) var(--space-normal) var(--space-one)
|
||||
var(--space-one);
|
||||
position: relative;
|
||||
|
||||
&:hover {
|
||||
@@ -60,14 +61,18 @@
|
||||
.hamburger--menu {
|
||||
cursor: pointer;
|
||||
display: block;
|
||||
margin-right: $space-normal;
|
||||
margin-right: var(--space-normal);
|
||||
}
|
||||
|
||||
.header--icon {
|
||||
display: block;
|
||||
margin-right: $space-normal;
|
||||
margin: 0 var(--space-small) 0 var(--space-smaller);
|
||||
|
||||
@media screen and (max-width: 1200px) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.header-title {
|
||||
margin: 0 var(--space-small);
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
border-top-width: 0;
|
||||
display: flex;
|
||||
min-width: var(--space-mega);
|
||||
padding: $zero $space-normal;
|
||||
padding: 0 var(--space-normal);
|
||||
}
|
||||
|
||||
.tabs--with-scroll {
|
||||
@@ -37,26 +37,26 @@
|
||||
|
||||
.tabs-title {
|
||||
a {
|
||||
font-size: $font-size-default;
|
||||
font-weight: $font-weight-medium;
|
||||
padding-bottom: $space-slab;
|
||||
padding-top: $space-slab;
|
||||
font-size: var(--font-size-default);
|
||||
font-weight: var(--font-weight-medium);
|
||||
padding-bottom: var(--space-slab);
|
||||
padding-top: var(--space-slab);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.tabs-title {
|
||||
flex-shrink: 0;
|
||||
margin: $zero $space-slab;
|
||||
margin: 0 var(--space-small);
|
||||
|
||||
.badge {
|
||||
background: $color-background;
|
||||
border-radius: $space-small;
|
||||
color: $color-gray;
|
||||
font-size: $font-size-micro;
|
||||
font-weight: $font-weight-black;
|
||||
margin-left: $space-smaller;
|
||||
padding: $space-smaller;
|
||||
background: var(--color-background);
|
||||
border-radius: var(--space-small);
|
||||
color: var(--color-gray);
|
||||
font-size: var(--font-size-micro);
|
||||
font-weight: var(--font-weight-black);
|
||||
margin: 0 var(--space-smaller);
|
||||
padding: var(--space-smaller);
|
||||
}
|
||||
|
||||
&:first-child {
|
||||
@@ -80,7 +80,7 @@
|
||||
color: $medium-gray;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
font-size: $font-size-small;
|
||||
font-size: var(--font-size-small);
|
||||
position: relative;
|
||||
top: 1px;
|
||||
transition: border-color 0.15s $swift-ease-out-function;
|
||||
@@ -88,13 +88,13 @@
|
||||
|
||||
&.is-active {
|
||||
a {
|
||||
border-bottom-color: $color-woot;
|
||||
color: $color-woot;
|
||||
border-bottom-color: var(--color-woot);
|
||||
color: var(--color-woot);
|
||||
}
|
||||
|
||||
.badge {
|
||||
background: $color-extra-light-blue;
|
||||
color: $color-woot;
|
||||
color: var(--color-woot);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
table {
|
||||
border-spacing: 0;
|
||||
font-size: $font-size-small;
|
||||
font-size: var(--font-size-small);
|
||||
|
||||
thead {
|
||||
th {
|
||||
font-weight: $font-weight-bold;
|
||||
font-weight: var(--font-weight-bold);
|
||||
text-align: left;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
}
|
||||
|
||||
tbody {
|
||||
tr {
|
||||
border-bottom: 1px solid $color-border-light;
|
||||
border-bottom: 1px solid var(--color-border-light);
|
||||
}
|
||||
|
||||
td {
|
||||
padding: $space-one $space-small;
|
||||
padding: var(--space-small);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -36,7 +37,7 @@ table {
|
||||
|
||||
.agent-name {
|
||||
display: block;
|
||||
font-weight: $font-weight-medium;
|
||||
font-weight: var(--font-weight-medium);
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
|
||||
@@ -53,11 +53,10 @@
|
||||
<woot-button
|
||||
v-else
|
||||
v-tooltip.right="$t('FILTER.TOOLTIP_LABEL')"
|
||||
variant="clear"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
icon="filter"
|
||||
size="small"
|
||||
class="btn-filter"
|
||||
size="tiny"
|
||||
@click="onToggleAdvanceFiltersModal"
|
||||
/>
|
||||
</div>
|
||||
@@ -838,10 +837,6 @@ export default {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.btn-filter {
|
||||
margin: 0 var(--space-smaller);
|
||||
}
|
||||
|
||||
.filter__applied {
|
||||
padding: 0 0 var(--space-slab) 0 !important;
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
@@ -850,4 +845,14 @@ export default {
|
||||
.delete-custom-view__button {
|
||||
margin-right: var(--space-normal);
|
||||
}
|
||||
|
||||
.tab--chat-type {
|
||||
padding: 0 var(--space-normal);
|
||||
|
||||
::v-deep {
|
||||
.tabs {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
63
app/javascript/dashboard/components/MaskedText.vue
Normal file
63
app/javascript/dashboard/components/MaskedText.vue
Normal file
@@ -0,0 +1,63 @@
|
||||
<template>
|
||||
<div class="text--container">
|
||||
<woot-button size="small" class=" button--text" @click="onCopy">
|
||||
{{ $t('COMPONENTS.CODE.BUTTON_TEXT') }}
|
||||
</woot-button>
|
||||
<woot-button
|
||||
variant="clear"
|
||||
size="small"
|
||||
class="button--visibility"
|
||||
color-scheme="secondary"
|
||||
:icon="masked ? 'eye-show' : 'eye-hide'"
|
||||
@click.prevent="toggleMasked"
|
||||
/>
|
||||
<highlightjs v-if="value" :code="masked ? '•'.repeat(10) : value" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import 'highlight.js/styles/default.css';
|
||||
import { copyTextToClipboard } from 'shared/helpers/clipboard';
|
||||
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
masked: true,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
async onCopy(e) {
|
||||
e.preventDefault();
|
||||
await copyTextToClipboard(this.value);
|
||||
bus.$emit('newToastMessage', this.$t('COMPONENTS.CODE.COPY_SUCCESSFUL'));
|
||||
},
|
||||
toggleMasked() {
|
||||
this.masked = !this.masked;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.text--container {
|
||||
position: relative;
|
||||
text-align: left;
|
||||
|
||||
.button--text,
|
||||
.button--visibility {
|
||||
margin-top: 0;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.button--visibility {
|
||||
right: 60px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -22,7 +22,6 @@ export default {
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.toggle-sidebar {
|
||||
margin-right: var(--space-small);
|
||||
margin-left: var(--space-minus-small);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -181,7 +181,7 @@ export default {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: var(--space-smaller) 0 var(--space-smaller) var(--space-small);
|
||||
padding: var(--space-smaller);
|
||||
margin: 0;
|
||||
|
||||
.info-wrap {
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return { showSwitchButton: false };
|
||||
@@ -69,7 +70,7 @@ export default {
|
||||
}
|
||||
|
||||
.switch-button {
|
||||
margin-right: var(--space-small);
|
||||
margin: 0 var(--space-small);
|
||||
}
|
||||
|
||||
.account-context--switch-group {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<div
|
||||
v-if="show"
|
||||
v-on-clickaway="onClickAway"
|
||||
class="dropdown-pane"
|
||||
class="options-menu dropdown-pane"
|
||||
:class="{ 'dropdown-pane--open': show }"
|
||||
>
|
||||
<availability-status />
|
||||
@@ -150,10 +150,10 @@ export default {
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.dropdown-pane {
|
||||
.options-menu.dropdown-pane {
|
||||
left: var(--space-slab);
|
||||
bottom: var(--space-larger);
|
||||
min-width: 22rem;
|
||||
min-width: var(--space-giga);
|
||||
top: unset;
|
||||
z-index: var(--z-index-low);
|
||||
}
|
||||
|
||||
@@ -331,11 +331,11 @@ export default {
|
||||
.beta {
|
||||
padding-right: var(--space-smaller) !important;
|
||||
padding-left: var(--space-smaller) !important;
|
||||
margin-left: var(--space-smaller) !important;
|
||||
margin: 0 var(--space-smaller) !important;
|
||||
display: inline-block;
|
||||
font-size: var(--font-size-micro);
|
||||
font-weight: var(--font-weight-medium);
|
||||
line-height: 18px;
|
||||
line-height: 14px;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 2em;
|
||||
color: var(--g-800);
|
||||
@@ -348,7 +348,7 @@ export default {
|
||||
color: var(--s-600);
|
||||
font-size: var(--font-size-micro);
|
||||
font-weight: var(--font-weight-bold);
|
||||
margin-left: var(--space-smaller);
|
||||
margin: 0 var(--space-smaller);
|
||||
padding: var(--space-zero) var(--space-smaller);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
<a v-else :href="href" :style="anchorStyle">{{ title }}</a>
|
||||
<button
|
||||
v-if="showClose"
|
||||
class="label-close--button "
|
||||
class="label-close--button"
|
||||
:style="{ color: textColor }"
|
||||
@click="onClick"
|
||||
>
|
||||
@@ -108,6 +108,7 @@ export default {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
font-weight: var(--font-weight-medium);
|
||||
gap: var(--space-smaller);
|
||||
margin-right: var(--space-smaller);
|
||||
margin-bottom: var(--space-smaller);
|
||||
padding: var(--space-smaller);
|
||||
@@ -126,9 +127,6 @@ export default {
|
||||
.label--icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
.label-color-dot {
|
||||
margin-right: var(--space-smaller);
|
||||
}
|
||||
|
||||
&.small .label--icon,
|
||||
&.small .close--icon {
|
||||
@@ -209,7 +207,6 @@ export default {
|
||||
.label-close--button {
|
||||
color: var(--s-800);
|
||||
margin-bottom: var(--space-minus-micro);
|
||||
margin-left: var(--space-smaller);
|
||||
border-radius: var(--border-radius-small);
|
||||
cursor: pointer;
|
||||
|
||||
@@ -232,7 +229,6 @@ export default {
|
||||
width: var(--space-slab);
|
||||
height: var(--space-slab);
|
||||
border-radius: var(--border-radius-small);
|
||||
margin-right: var(--space-smaller);
|
||||
box-shadow: var(--shadow-small);
|
||||
}
|
||||
.label.small .label-color-dot {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<button class="back-button" @click.capture="goBack">
|
||||
<button class="settings back-button" @click.capture="goBack">
|
||||
<fluent-icon icon="chevron-left" />
|
||||
{{ buttonLabel || $t('GENERAL_SETTINGS.BACK') }}
|
||||
</button>
|
||||
@@ -29,4 +29,3 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style scoped></style>
|
||||
|
||||
@@ -25,13 +25,15 @@ export default {
|
||||
</script>
|
||||
<style scoped>
|
||||
.inbox--name {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
padding: var(--space-micro) 0;
|
||||
line-height: var(--space-slab);
|
||||
font-weight: var(--font-weight-medium);
|
||||
background: none;
|
||||
color: var(--s-500);
|
||||
color: var(--s-600);
|
||||
font-size: var(--font-size-mini);
|
||||
margin: 0 var(--space-one);
|
||||
}
|
||||
|
||||
.inbox--icon {
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<fluent-icon
|
||||
icon="chevron-left"
|
||||
size="18"
|
||||
class="margin-left-minus-slab"
|
||||
:class="pageFooterIconClass"
|
||||
/>
|
||||
</woot-button>
|
||||
<woot-button
|
||||
@@ -65,7 +65,7 @@
|
||||
<fluent-icon
|
||||
icon="chevron-right"
|
||||
size="18"
|
||||
class="margin-left-minus-slab"
|
||||
:class="pageFooterIconClass"
|
||||
/>
|
||||
</woot-button>
|
||||
</div>
|
||||
@@ -74,8 +74,11 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import rtlMixin from 'shared/mixins/rtlMixin';
|
||||
|
||||
export default {
|
||||
components: {},
|
||||
mixins: [rtlMixin],
|
||||
props: {
|
||||
currentPage: {
|
||||
type: Number,
|
||||
@@ -91,6 +94,11 @@ export default {
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
pageFooterIconClass() {
|
||||
return this.isRTLView
|
||||
? 'margin-right-minus-slab'
|
||||
: 'margin-left-minus-slab';
|
||||
},
|
||||
isFooterVisible() {
|
||||
return this.totalCount && !(this.firstIndex > this.totalCount);
|
||||
},
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
:username="user.name"
|
||||
:status="user.availability_status"
|
||||
/>
|
||||
<h6 class="text-block-title text-truncate text-capitalize">
|
||||
<h6 class="text-block-title user-name text-truncate text-capitalize">
|
||||
{{ user.name }}
|
||||
</h6>
|
||||
</div>
|
||||
@@ -38,11 +38,7 @@ export default {
|
||||
text-align: left;
|
||||
|
||||
.user-name {
|
||||
margin: 0;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.user-thumbnail-box {
|
||||
margin-right: var(--space-small);
|
||||
margin: 0 var(--space-small);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -44,3 +44,14 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.status--filter {
|
||||
background-color: var(--color-background-light);
|
||||
border: 1px solid var(--color-border);
|
||||
font-size: var(--font-size-mini);
|
||||
height: var(--space-medium);
|
||||
margin: 0 var(--space-smaller);
|
||||
padding: 0 var(--space-medium) 0 var(--space-small);
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div
|
||||
class="conversation-details-wrap"
|
||||
:class="{ 'with-border-left': !isOnExpandedLayout }"
|
||||
:class="{ 'with-border-right': !isOnExpandedLayout }"
|
||||
>
|
||||
<conversation-header
|
||||
v-if="currentChat.id"
|
||||
@@ -144,8 +144,8 @@ export default {
|
||||
width: 100%;
|
||||
background: var(--color-background-light);
|
||||
|
||||
&.with-border-left {
|
||||
border-left: 1px solid var(--color-border);
|
||||
&.with-border-right {
|
||||
border-right: 1px solid var(--color-border);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +164,7 @@ export default {
|
||||
}
|
||||
|
||||
.conversation-sidebar-wrap {
|
||||
border-right: 1px solid var(--color-border);
|
||||
height: auto;
|
||||
flex: 0 0;
|
||||
z-index: var(--z-index-low);
|
||||
|
||||
@@ -419,7 +419,6 @@ export default {
|
||||
.conversation--metadata {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding-right: var(--space-normal);
|
||||
|
||||
.label {
|
||||
background: none;
|
||||
@@ -432,6 +431,7 @@ export default {
|
||||
|
||||
.assignee-label {
|
||||
display: inline-flex;
|
||||
margin-left: var(--space-small);
|
||||
max-width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,30 +10,30 @@
|
||||
:status="currentContact.availability_status"
|
||||
/>
|
||||
<div class="user--profile__meta">
|
||||
<h3 class="user--name text-truncate">
|
||||
<span class="margin-right-smaller">{{ currentContact.name }}</span>
|
||||
<fluent-icon
|
||||
v-if="!isHMACVerified"
|
||||
v-tooltip="$t('CONVERSATION.UNVERIFIED_SESSION')"
|
||||
size="14"
|
||||
class="hmac-warning__icon"
|
||||
icon="warning"
|
||||
/>
|
||||
</h3>
|
||||
<woot-button
|
||||
variant="link"
|
||||
color-scheme="secondary"
|
||||
class="text-truncate"
|
||||
@click.prevent="$emit('contact-panel-toggle')"
|
||||
>
|
||||
<h3 class="sub-block-title user--name text-truncate">
|
||||
<span>{{ currentContact.name }}</span>
|
||||
<fluent-icon
|
||||
v-if="!isHMACVerified"
|
||||
v-tooltip="$t('CONVERSATION.UNVERIFIED_SESSION')"
|
||||
size="14"
|
||||
class="hmac-warning__icon"
|
||||
icon="warning"
|
||||
/>
|
||||
</h3>
|
||||
</woot-button>
|
||||
<div class="conversation--header--actions text-truncate">
|
||||
<inbox-name
|
||||
v-if="hasMultipleInboxes"
|
||||
:inbox="inbox"
|
||||
class="margin-right-small"
|
||||
/>
|
||||
<span
|
||||
v-if="isSnoozed"
|
||||
class="snoozed--display-text margin-right-small"
|
||||
>
|
||||
<inbox-name v-if="hasMultipleInboxes" :inbox="inbox" />
|
||||
<span v-if="isSnoozed" class="snoozed--display-text">
|
||||
{{ snoozedDisplayText }}
|
||||
</span>
|
||||
<woot-button
|
||||
class="user--profile__button margin-right-small"
|
||||
class="user--profile__button"
|
||||
size="small"
|
||||
variant="link"
|
||||
@click="$emit('contact-panel-toggle')"
|
||||
@@ -204,17 +204,21 @@ export default {
|
||||
|
||||
.user--name {
|
||||
display: inline-block;
|
||||
font-size: var(--font-size-medium);
|
||||
line-height: 1.3;
|
||||
margin: 0;
|
||||
line-height: 1.2;
|
||||
text-transform: capitalize;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.conversation--header--actions {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-size: var(--font-size-mini);
|
||||
gap: var(--space-small);
|
||||
|
||||
::v-deep .inbox--name {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.user--profile__button {
|
||||
padding: 0;
|
||||
@@ -228,5 +232,6 @@ export default {
|
||||
|
||||
.hmac-warning__icon {
|
||||
color: var(--y-600);
|
||||
margin: 0 var(--space-micro);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -243,24 +243,20 @@ export default {
|
||||
return this.contentAttributes.translations || {};
|
||||
},
|
||||
displayQuotedButton() {
|
||||
if (!this.isIncoming) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.emailMessageContent.includes('<blockquote')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!this.isIncoming) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
translationsAvailable() {
|
||||
return !!Object.keys(this.translations).length;
|
||||
},
|
||||
message() {
|
||||
if (this.contentType === 'input_csat') {
|
||||
return this.$t('CONVERSATION.CSAT_REPLY_MESSAGE');
|
||||
}
|
||||
|
||||
// If the message is an email, emailMessageContent would be present
|
||||
// In that case, we would use letter package to render the email
|
||||
if (this.emailMessageContent && this.isIncoming) {
|
||||
@@ -278,6 +274,11 @@ export default {
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
if (this.contentType === 'input_csat') {
|
||||
return this.$t('CONVERSATION.CSAT_REPLY_MESSAGE') + botMessageContent;
|
||||
}
|
||||
|
||||
return (
|
||||
this.formatMessage(
|
||||
this.data.content,
|
||||
@@ -659,4 +660,71 @@ li.right {
|
||||
.context-menu {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* Markdown styling */
|
||||
|
||||
.bubble .text-content {
|
||||
p code {
|
||||
background-color: var(--s-75);
|
||||
display: inline-block;
|
||||
line-height: 1;
|
||||
|
||||
border-radius: var(--border-radius-small);
|
||||
padding: var(--space-smaller);
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: var(--s-75);
|
||||
border-color: var(--s-75);
|
||||
color: var(--s-800);
|
||||
border-radius: var(--border-radius-normal);
|
||||
padding: var(--space-small);
|
||||
margin-top: var(--space-smaller);
|
||||
margin-bottom: var(--space-small);
|
||||
display: block;
|
||||
line-height: 1.7;
|
||||
white-space: pre-wrap;
|
||||
|
||||
code {
|
||||
background-color: transparent;
|
||||
color: var(--s-800);
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: var(--space-micro) solid var(--s-75);
|
||||
color: var(--s-800);
|
||||
padding: var(--space-smaller) var(--space-small);
|
||||
margin: var(--space-smaller) 0;
|
||||
padding: var(--space-small) var(--space-small) 0 var(--space-normal);
|
||||
}
|
||||
}
|
||||
|
||||
.right .bubble .text-content {
|
||||
p code {
|
||||
background-color: var(--w-600);
|
||||
color: var(--white);
|
||||
}
|
||||
|
||||
pre {
|
||||
background-color: var(--w-800);
|
||||
border-color: var(--w-700);
|
||||
color: var(--white);
|
||||
|
||||
code {
|
||||
background-color: transparent;
|
||||
color: var(--white);
|
||||
}
|
||||
}
|
||||
|
||||
blockquote {
|
||||
border-left: var(--space-micro) solid var(--w-400);
|
||||
color: var(--white);
|
||||
|
||||
p {
|
||||
color: var(--w-75);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
<emoji-input
|
||||
v-if="showEmojiPicker"
|
||||
v-on-clickaway="hideEmojiPicker"
|
||||
:class="emojiDialogClassOnExpanedLayout"
|
||||
:class="emojiDialogClassOnExpandedLayoutAndRTLView"
|
||||
:on-click="emojiOnClick"
|
||||
/>
|
||||
<reply-email-head
|
||||
@@ -179,6 +179,7 @@ import { trimContent, debounce } from '@chatwoot/utils';
|
||||
import wootConstants from 'dashboard/constants';
|
||||
import { isEditorHotKeyEnabled } from 'dashboard/mixins/uiSettings';
|
||||
import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events';
|
||||
import rtlMixin from 'shared/mixins/rtlMixin';
|
||||
|
||||
const EmojiInput = () => import('shared/components/emoji/EmojiInput');
|
||||
|
||||
@@ -202,6 +203,7 @@ export default {
|
||||
uiSettingsMixin,
|
||||
alertMixin,
|
||||
messageFormatterMixin,
|
||||
rtlMixin,
|
||||
],
|
||||
props: {
|
||||
selectedTweet: {
|
||||
@@ -422,10 +424,14 @@ export default {
|
||||
} = this.uiSettings;
|
||||
return conversationDisplayType !== CONDENSED;
|
||||
},
|
||||
emojiDialogClassOnExpanedLayout() {
|
||||
return this.isOnExpandedLayout || this.popoutReplyBox
|
||||
? 'emoji-dialog--expanded'
|
||||
: '';
|
||||
emojiDialogClassOnExpandedLayoutAndRTLView() {
|
||||
if (this.isOnExpandedLayout || this.popoutReplyBox) {
|
||||
return 'emoji-dialog--expanded';
|
||||
}
|
||||
if (this.isRTLView) {
|
||||
return 'emoji-dialog--rtl';
|
||||
}
|
||||
return '';
|
||||
},
|
||||
replyToUserLength() {
|
||||
const selectedTweet = this.selectedTweet || {};
|
||||
@@ -881,6 +887,11 @@ export default {
|
||||
toggleTyping(status) {
|
||||
const conversationId = this.currentChat.id;
|
||||
const isPrivate = this.isPrivate;
|
||||
|
||||
if (!conversationId) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.$store.dispatch('conversationTypingStatus/toggleTyping', {
|
||||
status,
|
||||
conversationId,
|
||||
@@ -968,15 +979,9 @@ export default {
|
||||
},
|
||||
getMessagePayloadForWhatsapp(message) {
|
||||
const multipleMessagePayload = [];
|
||||
const messagePayload = {
|
||||
conversationId: this.currentChat.id,
|
||||
message,
|
||||
private: false,
|
||||
};
|
||||
|
||||
multipleMessagePayload.push(messagePayload);
|
||||
|
||||
if (this.attachedFiles && this.attachedFiles.length) {
|
||||
let caption = message;
|
||||
this.attachedFiles.forEach(attachment => {
|
||||
const attachedFile = this.globalConfig.directUploadsEnabled
|
||||
? attachment.blobSignedId
|
||||
@@ -985,10 +990,18 @@ export default {
|
||||
conversationId: this.currentChat.id,
|
||||
files: [attachedFile],
|
||||
private: false,
|
||||
message: '',
|
||||
message: caption,
|
||||
};
|
||||
multipleMessagePayload.push(attachmentPayload);
|
||||
caption = '';
|
||||
});
|
||||
} else {
|
||||
const messagePayload = {
|
||||
conversationId: this.currentChat.id,
|
||||
message,
|
||||
private: false,
|
||||
};
|
||||
multipleMessagePayload.push(messagePayload);
|
||||
}
|
||||
|
||||
return multipleMessagePayload;
|
||||
@@ -1096,7 +1109,7 @@ export default {
|
||||
|
||||
.emoji-dialog {
|
||||
top: unset;
|
||||
bottom: var(--space-normal);
|
||||
bottom: -40px;
|
||||
left: -320px;
|
||||
right: unset;
|
||||
|
||||
@@ -1107,6 +1120,19 @@ export default {
|
||||
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.08));
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-dialog--rtl {
|
||||
left: unset;
|
||||
right: -320px;
|
||||
&::before {
|
||||
left: var(--space-minus-normal);
|
||||
transform: rotate(90deg);
|
||||
right: 0;
|
||||
bottom: var(--space-small);
|
||||
filter: drop-shadow(0px 4px 4px rgba(0, 0, 0, 0.08));
|
||||
}
|
||||
}
|
||||
|
||||
.emoji-dialog--expanded {
|
||||
left: unset;
|
||||
bottom: var(--space-jumbo);
|
||||
|
||||
@@ -739,4 +739,9 @@ export const getLanguageName = (languageCode = '') => {
|
||||
return languageObj.name || '';
|
||||
};
|
||||
|
||||
export const getLanguageDirection = (languageCode = '') => {
|
||||
const rtlLanguageIds = ['ar', 'as', 'fa', 'he', 'ku', 'ur'];
|
||||
return rtlLanguageIds.includes(languageCode);
|
||||
};
|
||||
|
||||
export default languages;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { getLanguageName } from '../languages';
|
||||
import { getLanguageName, getLanguageDirection } from '../languages';
|
||||
|
||||
describe('#getLanguageName', () => {
|
||||
it('Returns correct language name', () => {
|
||||
@@ -8,3 +8,10 @@ describe('#getLanguageName', () => {
|
||||
expect(getLanguageName('')).toEqual('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('#getLanguageDirection', () => {
|
||||
it('Returns correct language direction', () => {
|
||||
expect(getLanguageDirection('es')).toEqual(false);
|
||||
expect(getLanguageDirection('ar')).toEqual(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
<div
|
||||
class="message-text__wrap"
|
||||
:class="{
|
||||
'show--quoted': showQuotedContent,
|
||||
'hide--quoted': !showQuotedContent,
|
||||
'show--quoted': isQuotedContentPresent,
|
||||
'hide--quoted': !isQuotedContentPresent,
|
||||
}"
|
||||
>
|
||||
<div v-if="!isEmail" v-dompurify-html="message" class="text-content" />
|
||||
<letter v-else class="text-content" :html="message" />
|
||||
<button
|
||||
v-if="displayQuotedButton"
|
||||
v-if="showQuoteToggle"
|
||||
class="quoted-text--button"
|
||||
@click="toggleQuotedContent"
|
||||
>
|
||||
@@ -49,6 +49,20 @@ export default {
|
||||
showQuotedContent: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isQuotedContentPresent() {
|
||||
if (!this.isEmail) {
|
||||
return this.message.includes('<blockquote');
|
||||
}
|
||||
return this.showQuotedContent;
|
||||
},
|
||||
showQuoteToggle() {
|
||||
if (!this.isEmail) {
|
||||
return false;
|
||||
}
|
||||
return this.displayQuotedButton;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
toggleQuotedContent() {
|
||||
this.showQuotedContent = !this.showQuotedContent;
|
||||
|
||||
@@ -51,15 +51,11 @@ export default {
|
||||
border-radius: var(--border-radius-small);
|
||||
overflow: hidden;
|
||||
.menu-label {
|
||||
margin: 0;
|
||||
margin: 0 var(--space-small);
|
||||
font-size: var(--font-size-mini);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
margin-right: var(--space-small);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: var(--w-500);
|
||||
color: var(--white);
|
||||
@@ -68,7 +64,6 @@ export default {
|
||||
|
||||
.agent-thumbnail {
|
||||
margin-top: 0 !important;
|
||||
margin-right: var(--space-small);
|
||||
}
|
||||
|
||||
.label-pill {
|
||||
@@ -77,6 +72,5 @@ export default {
|
||||
border-radius: var(--border-radius-rounded);
|
||||
border: 1px solid var(--s-50);
|
||||
flex-shrink: 0;
|
||||
margin-right: var(--space-small);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -43,13 +43,9 @@ export default {
|
||||
align-items: center;
|
||||
|
||||
.menu-label {
|
||||
margin: 0;
|
||||
margin: 0 var(--space-small);
|
||||
font-size: var(--font-size-mini);
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
margin-right: var(--space-small);
|
||||
}
|
||||
}
|
||||
|
||||
.submenu {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div class="bulk-action__agents">
|
||||
<div v-on-clickaway="onCloseAgentList" class="bulk-action__agents">
|
||||
<div class="triangle" :style="cssVars">
|
||||
<svg height="12" viewBox="0 0 24 12" width="24">
|
||||
<path
|
||||
@@ -50,7 +50,6 @@
|
||||
:status="agent.availability_status"
|
||||
:username="agent.name"
|
||||
size="22px"
|
||||
class="margin-right-small"
|
||||
/>
|
||||
<span class="reports-option__title">{{ agent.name }}</span>
|
||||
</div>
|
||||
@@ -127,6 +126,7 @@ export default {
|
||||
return {
|
||||
query: '',
|
||||
selectedAgent: null,
|
||||
goBackToAgentList: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
@@ -170,6 +170,7 @@ export default {
|
||||
this.$emit('select', this.selectedAgent);
|
||||
},
|
||||
goBack() {
|
||||
this.goBackToAgentList = true;
|
||||
this.selectedAgent = null;
|
||||
},
|
||||
assignAgent(agent) {
|
||||
@@ -178,6 +179,12 @@ export default {
|
||||
onClose() {
|
||||
this.$emit('close');
|
||||
},
|
||||
onCloseAgentList() {
|
||||
if (this.selectedAgent === null && !this.goBackToAgentList) {
|
||||
this.onClose();
|
||||
}
|
||||
this.goBackToAgentList = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
@@ -184,9 +184,18 @@ export default {
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
// For RTL direction view
|
||||
.app-rtl--wrapper {
|
||||
.bulk-action__actions {
|
||||
::v-deep .button--only-icon:last-child {
|
||||
margin-right: var(--space-smaller);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.bulk-action__container {
|
||||
border-bottom: 1px solid var(--s-100);
|
||||
padding: var(--space-normal) var(--space-one);
|
||||
padding: var(--space-normal);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
@@ -195,7 +204,7 @@ export default {
|
||||
|
||||
span {
|
||||
font-size: var(--font-size-mini);
|
||||
margin-left: var(--space-smaller);
|
||||
margin: 0 var(--space-smaller);
|
||||
}
|
||||
|
||||
input[type='checkbox'] {
|
||||
|
||||
@@ -97,7 +97,7 @@ export default {
|
||||
}
|
||||
|
||||
.label-container {
|
||||
margin-top: var(--space-micro);
|
||||
margin: var(--space-micro) var(--space-small) 0;
|
||||
}
|
||||
|
||||
.labels-wrap {
|
||||
|
||||
@@ -5,7 +5,7 @@ export const CONVERSATION_EVENTS = Object.freeze({
|
||||
INSERTED_A_CANNED_RESPONSE: 'Inserted a canned response',
|
||||
INSERTED_A_VARIABLE: 'Inserted a variable',
|
||||
USED_MENTIONS: 'Used mentions',
|
||||
|
||||
SEARCH_CONVERSATION: 'Searched conversations',
|
||||
APPLY_FILTER: 'Applied filters in the conversation list',
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import AnalyticsHelper from './AnalyticsHelper';
|
||||
import LogRocket from 'logrocket';
|
||||
import DashboardAudioNotificationHelper from './AudioAlerts/DashboardAudioNotificationHelper';
|
||||
|
||||
export const CHATWOOT_SET_USER = 'CHATWOOT_SET_USER';
|
||||
@@ -10,6 +11,12 @@ export const ANALYTICS_RESET = 'ANALYTICS_RESET';
|
||||
export const initializeAnalyticsEvents = () => {
|
||||
window.bus.$on(ANALYTICS_IDENTITY, ({ user }) => {
|
||||
AnalyticsHelper.identify(user);
|
||||
if (window.logRocketProjectId) {
|
||||
LogRocket.identify(user.id, {
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
});
|
||||
}
|
||||
});
|
||||
window.bus.$on(ANALYTICS_RESET, () => {});
|
||||
};
|
||||
|
||||
@@ -33,6 +33,7 @@ import uk from './locale/uk';
|
||||
import vi from './locale/vi';
|
||||
import zh_CN from './locale/zh_CN';
|
||||
import zh_TW from './locale/zh_TW';
|
||||
import is from './locale/is';
|
||||
|
||||
export default {
|
||||
ar,
|
||||
@@ -70,4 +71,5 @@ export default {
|
||||
vi,
|
||||
zh_CN,
|
||||
zh_TW,
|
||||
is,
|
||||
};
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"REMOVE_SELECTION": "إزالة التحديد",
|
||||
"DOWNLOAD": "تنزيل",
|
||||
"UNKNOWN_FILE_TYPE": "ملف غير معروف",
|
||||
"SAVE_CONTACT": "Save",
|
||||
"SAVE_CONTACT": "حفظ",
|
||||
"UPLOADING_ATTACHMENTS": "جاري تحميل المرفقات...",
|
||||
"SUCCESS_DELETE_MESSAGE": "تم حذف الرسالة بنجاح",
|
||||
"FAIL_DELETE_MESSSAGE": "تعذر حذف الرسالة! حاول مرة أخرى",
|
||||
@@ -43,8 +43,8 @@
|
||||
"RATING_TITLE": "التقييم",
|
||||
"FEEDBACK_TITLE": "الملاحظات",
|
||||
"CARD": {
|
||||
"SHOW_LABELS": "Show labels",
|
||||
"HIDE_LABELS": "Hide labels"
|
||||
"SHOW_LABELS": "إظهار السمات",
|
||||
"HIDE_LABELS": "إخفاء السمات"
|
||||
},
|
||||
"HEADER": {
|
||||
"RESOLVE_ACTION": "إغلاق المحادثة",
|
||||
@@ -69,7 +69,7 @@
|
||||
"CARD_CONTEXT_MENU": {
|
||||
"PENDING": "تحديد كمعلق",
|
||||
"RESOLVED": "تحديد كمحلولة",
|
||||
"MARK_AS_UNREAD": "Mark as unread",
|
||||
"MARK_AS_UNREAD": "وضع علامة كغير مقروء",
|
||||
"REOPEN": "إعادة فتح المحادثة",
|
||||
"SNOOZE": {
|
||||
"TITLE": "غفوة",
|
||||
@@ -135,8 +135,8 @@
|
||||
}
|
||||
},
|
||||
"UNDEFINED_VARIABLES": {
|
||||
"TITLE": "Undefined variables",
|
||||
"MESSAGE": "You have {undefinedVariablesCount} undefined variables in your message: {undefinedVariables}. Would you like to send the message anyway?",
|
||||
"TITLE": "متغيرات غير معرفة",
|
||||
"MESSAGE": "لديك {undefinedVariablesCount} متغيرات غير محددة في رسالتك: {undefinedVariables}. هل ترغب في إرسال الرسالة على أي حال؟",
|
||||
"CONFIRM": {
|
||||
"YES": "إرسال",
|
||||
"CANCEL": "إلغاء"
|
||||
@@ -165,7 +165,8 @@
|
||||
"CONTEXT_MENU": {
|
||||
"COPY": "نسخ",
|
||||
"DELETE": "حذف",
|
||||
"CREATE_A_CANNED_RESPONSE": "إضافة إلى الردود السريعة"
|
||||
"CREATE_A_CANNED_RESPONSE": "إضافة إلى الردود السريعة",
|
||||
"TRANSLATE": "ترجم"
|
||||
}
|
||||
},
|
||||
"EMAIL_TRANSCRIPT": {
|
||||
@@ -253,5 +254,29 @@
|
||||
"BCC": "Bcc",
|
||||
"CC": "Cc",
|
||||
"SUBJECT": "الموضوع"
|
||||
},
|
||||
"CONVERSATION_PARTICIPANTS": {
|
||||
"SIDEBAR_MENU_TITLE": "شارك",
|
||||
"SIDEBAR_TITLE": "المشاركون في المحادثة",
|
||||
"NO_RECORDS_FOUND": "لم يتم العثور على النتائج",
|
||||
"ADD_PARTICIPANTS": "اختر المشاركين",
|
||||
"REMANING_PARTICIPANTS_TEXT": "+%{count} أخرى",
|
||||
"REMANING_PARTICIPANT_TEXT": "+%{count} أخرى",
|
||||
"TOTAL_PARTICIPANTS_TEXT": "%{count} شخص مشارك.",
|
||||
"TOTAL_PARTICIPANT_TEXT": "%{count} شخص مشارك.",
|
||||
"NO_PARTICIPANTS_TEXT": "لا أحد يشارك !.",
|
||||
"WATCH_CONVERSATION": "الانضمام إلى المحادثة",
|
||||
"YOU_ARE_WATCHING": "أنت مشترك",
|
||||
"API": {
|
||||
"ERROR_MESSAGE": "تعذر التحديث، حاول مرة أخرى!",
|
||||
"SUCCESS_MESSAGE": "تم تحديث المشاركين!"
|
||||
}
|
||||
},
|
||||
"TRANSLATE_MODAL": {
|
||||
"TITLE": "عرض المحتوى المترجم",
|
||||
"DESC": "يمكنك عرض المحتوى المترجم في كل لغة.",
|
||||
"ORIGINAL_CONTENT": "المحتوى الأصلي",
|
||||
"TRANSLATED_CONTENT": "المحتوى المترجم",
|
||||
"NO_TRANSLATIONS_AVAILABLE": "لا توجد ترجمات لهذا المحتوى"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
"conversation_creation": "محادثة جديدة",
|
||||
"conversation_assignment": "تم تعيين المحادثة",
|
||||
"assigned_conversation_new_message": "رسالة جديدة",
|
||||
"participating_conversation_new_message": "رسالة جديدة",
|
||||
"conversation_mention": "إشارة"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -28,6 +28,17 @@
|
||||
"SAVING": "جاري الحفظ...",
|
||||
"SAVED": "تم الحفظ"
|
||||
},
|
||||
"ARTICLE_EDITOR": {
|
||||
"IMAGE_UPLOAD": {
|
||||
"TITLE": "رفع صورة",
|
||||
"UPLOADING": "جاري الرفع...",
|
||||
"SUCCESS": "Image uploaded successfully",
|
||||
"ERROR": "Error while uploading image",
|
||||
"ERROR_FILE_SIZE": "Image size should be less than {size}MB",
|
||||
"ERROR_FILE_FORMAT": "Image format should be jpg, jpeg or png",
|
||||
"ERROR_FILE_DIMENSIONS": "Image dimensions should be less than 2000 x 2000"
|
||||
}
|
||||
},
|
||||
"ARTICLE_SETTINGS": {
|
||||
"TITLE": "إعدادات المقالة",
|
||||
"FORM": {
|
||||
|
||||
@@ -476,7 +476,8 @@
|
||||
"WHATSAPP_SECTION_TITLE": "مفتاح API",
|
||||
"WHATSAPP_SECTION_UPDATE_TITLE": "Update API Key",
|
||||
"WHATSAPP_SECTION_UPDATE_PLACEHOLDER": "Enter the new API Key here",
|
||||
"WHATSAPP_SECTION_UPDATE_BUTTON": "تحديث"
|
||||
"WHATSAPP_SECTION_UPDATE_BUTTON": "تحديث",
|
||||
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
|
||||
},
|
||||
"AUTO_ASSIGNMENT": {
|
||||
"MAX_ASSIGNMENT_LIMIT": "حد الإسناد التلقائي",
|
||||
|
||||
@@ -23,6 +23,7 @@ import { default as _settings } from './settings.json';
|
||||
import { default as _signup } from './signup.json';
|
||||
import { default as _teamsSettings } from './teamsSettings.json';
|
||||
import { default as _whatsappTemplates } from './whatsappTemplates.json';
|
||||
import { default as _helpCenter } from './helpCenter.json';
|
||||
|
||||
export default {
|
||||
..._advancedFilters,
|
||||
@@ -50,4 +51,5 @@ export default {
|
||||
..._teamsSettings,
|
||||
..._whatsappTemplates,
|
||||
..._bulkActions,
|
||||
..._helpCenter,
|
||||
};
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
"ERROR_MESSAGE": "تعذر الاتصال بالخادم، الرجاء المحاولة مرة أخرى لاحقاً",
|
||||
"UNAUTH": "اسم المستخدم / كلمة المرور غير صحيحة. الرجاء المحاولة مرة أخرى"
|
||||
},
|
||||
"OAUTH": {
|
||||
"GOOGLE_LOGIN": "Login with Google",
|
||||
"BUSINESS_ACCOUNTS_ONLY": "Please use your company email address to login",
|
||||
"NO_ACCOUNT_FOUND": "We couldn't find an account for your email address."
|
||||
},
|
||||
"FORGOT_PASSWORD": "نسيت كلمة المرور؟",
|
||||
"CREATE_NEW_ACCOUNT": "إنشاء حساب جديد",
|
||||
"SUBMIT": "تسجيل الدخول"
|
||||
|
||||
@@ -426,6 +426,12 @@
|
||||
"UNATTENDED": "بدون حضور",
|
||||
"UNASSIGNED": "غير مسند"
|
||||
},
|
||||
"CONVERSATION_HEATMAP": {
|
||||
"HEADER": "Conversation Traffic",
|
||||
"NO_CONVERSATIONS": "No conversations",
|
||||
"CONVERSATION": "%{count} conversation",
|
||||
"CONVERSATIONS": "%{count} conversations"
|
||||
},
|
||||
"AGENT_CONVERSATIONS": {
|
||||
"HEADER": "المحادثات من قبل الوكلاء",
|
||||
"LOADING_MESSAGE": "جاري تحميل مقاييس الوكيل...",
|
||||
@@ -443,5 +449,14 @@
|
||||
"BUSY": "مشغول",
|
||||
"OFFLINE": "غير متصل"
|
||||
}
|
||||
},
|
||||
"DAYS_OF_WEEK": {
|
||||
"SUNDAY": "Sunday",
|
||||
"MONDAY": "Monday",
|
||||
"TUESDAY": "Tuesday",
|
||||
"WEDNESDAY": "Wednesday",
|
||||
"THURSDAY": "Thursday",
|
||||
"FRIDAY": "Friday",
|
||||
"SATURDAY": "Saturday"
|
||||
}
|
||||
}
|
||||
|
||||
23
app/javascript/dashboard/i18n/locale/ar/search.json
Normal file
23
app/javascript/dashboard/i18n/locale/ar/search.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"SEARCH": {
|
||||
"TABS": {
|
||||
"ALL": "الكل",
|
||||
"CONTACTS": "جهات الاتصال",
|
||||
"CONVERSATIONS": "المحادثات",
|
||||
"MESSAGES": "الرسائل"
|
||||
},
|
||||
"SECTION": {
|
||||
"CONTACTS": "جهات الاتصال",
|
||||
"CONVERSATIONS": "المحادثات",
|
||||
"MESSAGES": "الرسائل"
|
||||
},
|
||||
"EMPTY_STATE": "No %{item} found for query '%{query}'",
|
||||
"EMPTY_STATE_FULL": "No results found for query '%{query}'",
|
||||
"PLACEHOLDER_KEYBINDING": "/ to focus",
|
||||
"INPUT_PLACEHOLDER": "Search messages, contacts or conversations",
|
||||
"EMPTY_STATE_DEFAULT": "Search by conversation id, email, phone number, messages for better search results.",
|
||||
"BOT_LABEL": "رد آلي",
|
||||
"READ_MORE": "Read more",
|
||||
"WROTE": "wrote:"
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,8 @@
|
||||
"CONVERSATION_ASSIGNMENT": "إرسال إشعارات البريد الإلكتروني عند إسناد محادثة لي",
|
||||
"CONVERSATION_CREATION": "إرسال إشعارات للبريد الإلكتروني عند ورود محادثة جديدة",
|
||||
"CONVERSATION_MENTION": "إرسال إشعارات بالبريد الإلكتروني عندما يتم ذكرك في محادثة",
|
||||
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "إرسال إشعارات البريد الإلكتروني عند إنشاء رسالة جديدة في محادثة موكلة"
|
||||
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "إرسال إشعارات البريد الإلكتروني عند إنشاء رسالة جديدة في محادثة موكلة",
|
||||
"PARTICIPATING_CONVERSATION_NEW_MESSAGE": "إرسال إشعارات التنية عند إنشاء رسالة جديدة في محادثة موكلة"
|
||||
},
|
||||
"API": {
|
||||
"UPDATE_SUCCESS": "يتم تحديث إعدادات الإشعارات بنجاح",
|
||||
@@ -92,6 +93,7 @@
|
||||
"CONVERSATION_CREATION": "إرسال إشعارات المتصفح عند ورود محادثة جديدة",
|
||||
"CONVERSATION_MENTION": "إرسال إشعارات بالبريد الإلكتروني عندما يتم ذكرك او الاشارة اليك في محادثة",
|
||||
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "إرسال إشعارات التنية عند إنشاء رسالة جديدة في محادثة موكلة",
|
||||
"PARTICIPATING_CONVERSATION_NEW_MESSAGE": "إرسال إشعارات التنية عند إنشاء رسالة جديدة في محادثة موكلة",
|
||||
"HAS_ENABLED_PUSH": "لقد قمت بتمكين الإشعارات لهذا المتصفح.",
|
||||
"REQUEST_PUSH": "تفعيل إشعارات المتصفح"
|
||||
},
|
||||
@@ -192,6 +194,7 @@
|
||||
"CONVERSATIONS": "المحادثات",
|
||||
"ALL_CONVERSATIONS": "كل المحادثات",
|
||||
"MENTIONED_CONVERSATIONS": "الإشارات",
|
||||
"PARTICIPATING_CONVERSATIONS": "شارك",
|
||||
"UNATTENDED_CONVERSATIONS": "بدون حضور",
|
||||
"REPORTS": "التقارير",
|
||||
"SETTINGS": "الإعدادات",
|
||||
@@ -239,6 +242,7 @@
|
||||
"DRAFT": "مسودة",
|
||||
"ARCHIVED": "مؤرشفة",
|
||||
"CATEGORY": "الفئة",
|
||||
"SETTINGS": "الإعدادات",
|
||||
"CATEGORY_EMPTY_MESSAGE": "لم يتم العثور على فئات"
|
||||
},
|
||||
"SET_AUTO_OFFLINE": {
|
||||
|
||||
@@ -5,6 +5,9 @@
|
||||
"TESTIMONIAL_HEADER": "إن كل ما يلزم هو خطوة واحدة للمضي قدما",
|
||||
"TESTIMONIAL_CONTENT": "You're one step away from engaging your customers, retaining them and finding new ones.",
|
||||
"TERMS_ACCEPT": "من خلال التسجيل، فإنك توافق على <a href=\"https://www.chatwoot.com/terms\">شروط الخدمة</a> و <a href=\"https://www.chatwoot.com/privacy-policy\">سياسة الخصوصية</a>",
|
||||
"OAUTH": {
|
||||
"GOOGLE_SIGNUP": "Sign up with Google"
|
||||
},
|
||||
"COMPANY_NAME": {
|
||||
"LABEL": "Company name",
|
||||
"PLACEHOLDER": "Enter your company name. eg: Wayne Enterprises",
|
||||
|
||||
@@ -165,7 +165,8 @@
|
||||
"CONTEXT_MENU": {
|
||||
"COPY": "Copy",
|
||||
"DELETE": "Изтрий",
|
||||
"CREATE_A_CANNED_RESPONSE": "Add to canned responses"
|
||||
"CREATE_A_CANNED_RESPONSE": "Add to canned responses",
|
||||
"TRANSLATE": "Translate"
|
||||
}
|
||||
},
|
||||
"EMAIL_TRANSCRIPT": {
|
||||
@@ -253,5 +254,29 @@
|
||||
"BCC": "Bcc",
|
||||
"CC": "Cc",
|
||||
"SUBJECT": "Subject"
|
||||
},
|
||||
"CONVERSATION_PARTICIPANTS": {
|
||||
"SIDEBAR_MENU_TITLE": "Participating",
|
||||
"SIDEBAR_TITLE": "Conversation participants",
|
||||
"NO_RECORDS_FOUND": "Няма намерени резултати",
|
||||
"ADD_PARTICIPANTS": "Select participants",
|
||||
"REMANING_PARTICIPANTS_TEXT": "+%{count} others",
|
||||
"REMANING_PARTICIPANT_TEXT": "+%{count} other",
|
||||
"TOTAL_PARTICIPANTS_TEXT": "%{count} people are participating.",
|
||||
"TOTAL_PARTICIPANT_TEXT": "%{count} person is participating.",
|
||||
"NO_PARTICIPANTS_TEXT": "No one is participating!.",
|
||||
"WATCH_CONVERSATION": "Join conversation",
|
||||
"YOU_ARE_WATCHING": "You are participating",
|
||||
"API": {
|
||||
"ERROR_MESSAGE": "Could not update, try again!",
|
||||
"SUCCESS_MESSAGE": "Participants updated!"
|
||||
}
|
||||
},
|
||||
"TRANSLATE_MODAL": {
|
||||
"TITLE": "View translated content",
|
||||
"DESC": "You can view the translated content in each langauge.",
|
||||
"ORIGINAL_CONTENT": "Original Content",
|
||||
"TRANSLATED_CONTENT": "Translated Content",
|
||||
"NO_TRANSLATIONS_AVAILABLE": "No translations are available for this content"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@
|
||||
"conversation_creation": "New conversation",
|
||||
"conversation_assignment": "Conversation Assigned",
|
||||
"assigned_conversation_new_message": "New Message",
|
||||
"participating_conversation_new_message": "New Message",
|
||||
"conversation_mention": "Mention"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -28,6 +28,17 @@
|
||||
"SAVING": "Saving...",
|
||||
"SAVED": "Saved"
|
||||
},
|
||||
"ARTICLE_EDITOR": {
|
||||
"IMAGE_UPLOAD": {
|
||||
"TITLE": "Upload image",
|
||||
"UPLOADING": "Качване...",
|
||||
"SUCCESS": "Image uploaded successfully",
|
||||
"ERROR": "Error while uploading image",
|
||||
"ERROR_FILE_SIZE": "Image size should be less than {size}MB",
|
||||
"ERROR_FILE_FORMAT": "Image format should be jpg, jpeg or png",
|
||||
"ERROR_FILE_DIMENSIONS": "Image dimensions should be less than 2000 x 2000"
|
||||
}
|
||||
},
|
||||
"ARTICLE_SETTINGS": {
|
||||
"TITLE": "Article Settings",
|
||||
"FORM": {
|
||||
|
||||
@@ -476,7 +476,8 @@
|
||||
"WHATSAPP_SECTION_TITLE": "API Key",
|
||||
"WHATSAPP_SECTION_UPDATE_TITLE": "Update API Key",
|
||||
"WHATSAPP_SECTION_UPDATE_PLACEHOLDER": "Enter the new API Key here",
|
||||
"WHATSAPP_SECTION_UPDATE_BUTTON": "Обновяване"
|
||||
"WHATSAPP_SECTION_UPDATE_BUTTON": "Обновяване",
|
||||
"UPDATE_PRE_CHAT_FORM_SETTINGS": "Update Pre Chat Form Settings"
|
||||
},
|
||||
"AUTO_ASSIGNMENT": {
|
||||
"MAX_ASSIGNMENT_LIMIT": "Auto assignment limit",
|
||||
|
||||
@@ -23,6 +23,7 @@ import { default as _settings } from './settings.json';
|
||||
import { default as _signup } from './signup.json';
|
||||
import { default as _teamsSettings } from './teamsSettings.json';
|
||||
import { default as _whatsappTemplates } from './whatsappTemplates.json';
|
||||
import { default as _helpCenter } from './helpCenter.json';
|
||||
|
||||
export default {
|
||||
..._advancedFilters,
|
||||
@@ -50,4 +51,5 @@ export default {
|
||||
..._teamsSettings,
|
||||
..._whatsappTemplates,
|
||||
..._bulkActions,
|
||||
..._helpCenter,
|
||||
};
|
||||
|
||||
@@ -14,6 +14,11 @@
|
||||
"ERROR_MESSAGE": "Не можа да се свърже с Woot сървър. Моля, опитайте отново по-късно",
|
||||
"UNAUTH": "Username / Password Incorrect. Please try again"
|
||||
},
|
||||
"OAUTH": {
|
||||
"GOOGLE_LOGIN": "Login with Google",
|
||||
"BUSINESS_ACCOUNTS_ONLY": "Please use your company email address to login",
|
||||
"NO_ACCOUNT_FOUND": "We couldn't find an account for your email address."
|
||||
},
|
||||
"FORGOT_PASSWORD": "Forgot your password?",
|
||||
"CREATE_NEW_ACCOUNT": "Create new account",
|
||||
"SUBMIT": "Login"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user