Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ffe73a1ce | ||
|
|
76b0279571 | ||
|
|
76731ae31a | ||
|
|
e054c2a32f | ||
|
|
dbe27e081e | ||
|
|
f6a56edf86 | ||
|
|
66cb0ee865 | ||
|
|
74b9c79f49 | ||
|
|
d46f96e45c | ||
|
|
032699930c | ||
|
|
16085fdfbd | ||
|
|
00cbdaa8ca | ||
|
|
d0972a22b4 | ||
|
|
e707778490 | ||
|
|
0d894e0abc | ||
|
|
72f206025a | ||
|
|
70cb0a8ed9 | ||
|
|
d488a42664 | ||
|
|
9e4a5d028c | ||
|
|
9bbadc2388 | ||
|
|
ad782e450e | ||
|
|
98eb20d600 | ||
|
|
0ba5e47308 | ||
|
|
d041f5fc9f | ||
|
|
14382ad881 | ||
|
|
c16624dc5d | ||
|
|
60f16e8954 | ||
|
|
078ff615ee | ||
|
|
e7a52c3a46 | ||
|
|
add33d032c | ||
|
|
28c8023bad | ||
|
|
817ec4c50f | ||
|
|
ffb4bd0109 | ||
|
|
24cf7af30b | ||
|
|
0a65a233d7 | ||
|
|
4172cb4b23 | ||
|
|
cbfbe6dbad | ||
|
|
50894fd591 | ||
|
|
fbdc921ee5 | ||
|
|
3beafe31f8 | ||
|
|
74c6db37b3 | ||
|
|
4bbd56f87d | ||
|
|
b9ae664d47 | ||
|
|
269ad8f697 | ||
|
|
9cb75b55f7 | ||
|
|
fc06ee7726 | ||
|
|
88053c60c7 | ||
|
|
7b5f1e4876 | ||
|
|
c88ea257d5 | ||
|
|
dd0d221cfc | ||
|
|
ab87f9d6c0 | ||
|
|
e3f13dd455 | ||
|
|
4941c93cb0 | ||
|
|
c11eebb83a | ||
|
|
69bed3e08d | ||
|
|
cf48610f2f | ||
|
|
b179d1b563 | ||
|
|
34e509b9b7 | ||
|
|
6ffb7fe34e | ||
|
|
297e144f33 | ||
|
|
02a687b226 | ||
|
|
10a03cae24 | ||
|
|
e446a12ebb | ||
|
|
00eabe07a4 | ||
|
|
78251f54c9 | ||
|
|
d55988a6e0 | ||
|
|
37aa602c61 | ||
|
|
aba5a1a131 | ||
|
|
94ca2fd229 | ||
|
|
a3e5aad753 | ||
|
|
8e13a06833 | ||
|
|
38f9c3940b | ||
|
|
e026dc03e0 | ||
|
|
d911d38391 | ||
|
|
5d1be70e67 | ||
|
|
12b45f5c76 | ||
|
|
9f842bcd59 | ||
|
|
4fba9ef1fb | ||
|
|
cd3b6ebf28 | ||
|
|
11f6e6bc61 | ||
|
|
e5fd866a7c | ||
|
|
e5219df2b4 | ||
|
|
98c289dc3e | ||
|
|
3e91765472 | ||
|
|
1bf23055df | ||
|
|
2af337be10 | ||
|
|
dbb6c0a074 | ||
|
|
8c88344170 | ||
|
|
6a78254701 | ||
|
|
26ada8b342 | ||
|
|
3c6bd2c8fd | ||
|
|
2c2c47d7fd | ||
|
|
34f7405689 |
@@ -11,7 +11,6 @@ storage
|
||||
public/system
|
||||
tmp
|
||||
.codeclimate.yml
|
||||
public/assets
|
||||
public/packs
|
||||
node_modules
|
||||
vendor/bundle
|
||||
|
||||
+11
-2
@@ -35,7 +35,7 @@ REDIS_SENTINELS=
|
||||
REDIS_SENTINEL_MASTER_NAME=
|
||||
|
||||
# By default Chatwoot will pass REDIS_PASSWORD as the password value for sentinels
|
||||
# Use the following environment variable to customize passwords for sentinels.
|
||||
# Use the following environment variable to customize passwords for sentinels.
|
||||
# Use empty string if sentinels are configured with out passwords
|
||||
# REDIS_SENTINEL_PASSWORD=
|
||||
|
||||
@@ -45,7 +45,7 @@ REDIS_SENTINEL_MASTER_NAME=
|
||||
# REDIS_OPENSSL_VERIFY_MODE=none
|
||||
|
||||
# Postgres Database config variables
|
||||
# You can leave POSTGRES_DATABASE blank. The default name of
|
||||
# You can leave POSTGRES_DATABASE blank. The default name of
|
||||
# the database in the production environment is chatwoot_production
|
||||
# POSTGRES_DATABASE=
|
||||
POSTGRES_HOST=postgres
|
||||
@@ -213,3 +213,12 @@ STRIPE_WEBHOOK_SECRET=
|
||||
# Set to true if you want to upload files to cloud storage using the signed url
|
||||
# Make sure to follow https://edgeguides.rubyonrails.org/active_storage_overview.html#cross-origin-resource-sharing-cors-configuration on the cloud storage after setting this to true.
|
||||
DIRECT_UPLOADS_ENABLED=
|
||||
|
||||
#MS OAUTH creds
|
||||
AZURE_APP_ID=
|
||||
AZURE_APP_SECRET=
|
||||
|
||||
## Advanced configurations
|
||||
## Change these values to fine tune performance
|
||||
# control the concurrency setting of sidekiq
|
||||
# SIDEKIQ_CONCURRENCY=10
|
||||
|
||||
@@ -37,6 +37,8 @@ gem 'json_schemer'
|
||||
gem 'rack-attack'
|
||||
# a utility tool for streaming, flexible and safe downloading of remote files
|
||||
gem 'down', '~> 5.0'
|
||||
# authentication type to fetch and send mail over oauth2.0
|
||||
gem 'gmail_xoauth'
|
||||
|
||||
##-- for active storage --##
|
||||
gem 'aws-sdk-s3', require: false
|
||||
@@ -160,6 +162,8 @@ group :test do
|
||||
gem 'database_cleaner'
|
||||
# mock http calls
|
||||
gem 'webmock'
|
||||
# test profiling
|
||||
gem 'test-prof'
|
||||
end
|
||||
|
||||
group :development, :test do
|
||||
@@ -186,3 +190,5 @@ group :development, :test do
|
||||
gem 'spring'
|
||||
gem 'spring-watcher-listen'
|
||||
end
|
||||
# worked with microsoft refresh token
|
||||
gem 'omniauth-oauth2'
|
||||
|
||||
+28
-2
@@ -249,6 +249,8 @@ GEM
|
||||
gli (2.21.0)
|
||||
globalid (1.0.0)
|
||||
activesupport (>= 5.0)
|
||||
gmail_xoauth (0.4.2)
|
||||
oauth (>= 0.3.6)
|
||||
google-apis-core (0.7.0)
|
||||
addressable (~> 2.5, >= 2.5.1)
|
||||
googleauth (>= 0.16.2, < 2.a)
|
||||
@@ -333,8 +335,8 @@ GEM
|
||||
http-cookie (1.0.5)
|
||||
domain_name (~> 0.5)
|
||||
http-form_data (2.3.0)
|
||||
httparty (0.20.0)
|
||||
mime-types (~> 3.0)
|
||||
httparty (0.21.0)
|
||||
mini_mime (>= 1.0.0)
|
||||
multi_xml (>= 0.5.2)
|
||||
httpclient (2.8.3)
|
||||
i18n (1.11.0)
|
||||
@@ -437,6 +439,20 @@ GEM
|
||||
nokogiri (1.13.10-x86_64-linux)
|
||||
racc (~> 1.4)
|
||||
oauth (0.5.10)
|
||||
oauth2 (2.0.9)
|
||||
faraday (>= 0.17.3, < 3.0)
|
||||
jwt (>= 1.0, < 3.0)
|
||||
multi_xml (~> 0.5)
|
||||
rack (>= 1.2, < 4)
|
||||
snaky_hash (~> 2.0)
|
||||
version_gem (~> 1.1)
|
||||
omniauth (2.1.0)
|
||||
hashie (>= 3.4.6)
|
||||
rack (>= 2.2.3)
|
||||
rack-protection
|
||||
omniauth-oauth2 (1.8.0)
|
||||
oauth2 (>= 1.4, < 3)
|
||||
omniauth (~> 2.0)
|
||||
orm_adapter (0.5.0)
|
||||
os (1.1.4)
|
||||
parallel (1.22.1)
|
||||
@@ -465,6 +481,8 @@ GEM
|
||||
rack (>= 1.0, < 3)
|
||||
rack-cors (1.1.1)
|
||||
rack (>= 2.0.0)
|
||||
rack-protection (3.0.5)
|
||||
rack
|
||||
rack-proxy (0.7.2)
|
||||
rack
|
||||
rack-test (2.0.2)
|
||||
@@ -620,6 +638,9 @@ GEM
|
||||
gli
|
||||
hashie
|
||||
websocket-driver
|
||||
snaky_hash (2.0.1)
|
||||
hashie
|
||||
version_gem (~> 1.1, >= 1.1.1)
|
||||
spring (2.1.1)
|
||||
spring-watcher-listen (2.0.1)
|
||||
listen (>= 2.7, < 4.0)
|
||||
@@ -635,6 +656,7 @@ GEM
|
||||
statsd-ruby (1.5.0)
|
||||
stripe (6.5.0)
|
||||
telephone_number (1.4.16)
|
||||
test-prof (1.0.11)
|
||||
thor (1.2.1)
|
||||
tilt (2.0.10)
|
||||
time_diff (0.3.0)
|
||||
@@ -663,6 +685,7 @@ GEM
|
||||
valid_email2 (4.0.3)
|
||||
activemodel (>= 3.2)
|
||||
mail (~> 2.5)
|
||||
version_gem (1.1.1)
|
||||
warden (1.2.9)
|
||||
rack (>= 2.0.9)
|
||||
web-console (4.2.0)
|
||||
@@ -735,6 +758,7 @@ DEPENDENCIES
|
||||
flag_shih_tzu
|
||||
foreman
|
||||
geocoder
|
||||
gmail_xoauth
|
||||
google-cloud-dialogflow
|
||||
google-cloud-storage
|
||||
groupdate
|
||||
@@ -756,6 +780,7 @@ DEPENDENCIES
|
||||
maxminddb
|
||||
mock_redis
|
||||
newrelic_rpm
|
||||
omniauth-oauth2
|
||||
pg
|
||||
pg_search
|
||||
procore-sift
|
||||
@@ -791,6 +816,7 @@ DEPENDENCIES
|
||||
squasher
|
||||
stripe
|
||||
telephone_number
|
||||
test-prof
|
||||
time_diff
|
||||
twilio-ruby (~> 5.66)
|
||||
twitty
|
||||
|
||||
+2
-1
@@ -1,3 +1,4 @@
|
||||
backend: bin/rails s -p 3000
|
||||
frontend: bin/webpack-dev-server
|
||||
worker: bundle exec sidekiq -C config/sidekiq.yml
|
||||
# https://github.com/mperham/sidekiq/issues/3090#issuecomment-389748695
|
||||
worker: dotenv bundle exec sidekiq -C config/sidekiq.yml
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
backend: RAILS_ENV=test bin/rails s -p 5050
|
||||
frontend: bin/webpack-dev-server
|
||||
worker: RAILS_ENV=test bundle exec sidekiq -C config/sidekiq.yml
|
||||
worker: dotenv RAILS_ENV=test bundle exec sidekiq -C config/sidekiq.yml
|
||||
|
||||
@@ -119,4 +119,4 @@ Thanks goes to all these [wonderful people](https://www.chatwoot.com/docs/contri
|
||||
<a href="https://github.com/chatwoot/chatwoot/graphs/contributors"><img src="https://opencollective.com/chatwoot/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
|
||||
*Chatwoot* © 2017-2022, Chatwoot Inc - Released under the MIT License.
|
||||
*Chatwoot* © 2017-2023, Chatwoot Inc - Released under the MIT License.
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
class Api::V1::Accounts::Integrations::DyteController < Api::V1::Accounts::BaseController
|
||||
before_action :fetch_conversation, only: [:create_a_meeting]
|
||||
before_action :fetch_message, only: [:add_participant_to_meeting]
|
||||
before_action :authorize_request
|
||||
|
||||
def create_a_meeting
|
||||
render_response(dyte_processor_service.create_a_meeting(Current.user))
|
||||
end
|
||||
|
||||
def add_participant_to_meeting
|
||||
if @message.content_type != 'integrations'
|
||||
return render json: {
|
||||
error: I18n.t('errors.dyte.invalid_message_type')
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
render_response(
|
||||
dyte_processor_service.add_participant_to_meeting(@message.content_attributes['data']['meeting_id'], Current.user)
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authorize_request
|
||||
authorize @conversation.inbox, :show?
|
||||
end
|
||||
|
||||
def render_response(response)
|
||||
render json: response, status: response[:error].blank? ? :ok : :unprocessable_entity
|
||||
end
|
||||
|
||||
def dyte_processor_service
|
||||
Integrations::Dyte::ProcessorService.new(account: Current.account, conversation: @conversation)
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:conversation_id, :message_id)
|
||||
end
|
||||
|
||||
def fetch_conversation
|
||||
@conversation = Current.account.conversations.find_by!(display_id: permitted_params[:conversation_id])
|
||||
end
|
||||
|
||||
def fetch_message
|
||||
@message = Current.account.messages.find(permitted_params[:message_id])
|
||||
@conversation = @message.conversation
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
class Api::V1::Accounts::Microsoft::AuthorizationsController < Api::V1::Accounts::BaseController
|
||||
include MicrosoftConcern
|
||||
before_action :check_authorization
|
||||
|
||||
def create
|
||||
email = params[:authorization][:email]
|
||||
redirect_url = microsoft_client.auth_code.authorize_url(
|
||||
{
|
||||
redirect_uri: "#{base_url}/microsoft/callback",
|
||||
scope: 'offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send openid',
|
||||
prompt: 'consent'
|
||||
}
|
||||
)
|
||||
if redirect_url
|
||||
::Redis::Alfred.setex(email, Current.account.id, 5.minutes)
|
||||
render json: { success: true, url: redirect_url }
|
||||
else
|
||||
render json: { success: false }, status: :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def check_authorization
|
||||
raise Pundit::NotAuthorizedError unless Current.account_user.administrator?
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,36 @@
|
||||
class Api::V1::Widget::Integrations::DyteController < Api::V1::Widget::BaseController
|
||||
before_action :set_message
|
||||
|
||||
def add_participant_to_meeting
|
||||
if @message.content_type != 'integrations'
|
||||
return render json: {
|
||||
error: I18n.t('errors.dyte.invalid_message_type')
|
||||
}, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
response = dyte_processor_service.add_participant_to_meeting(
|
||||
@message.content_attributes['data']['meeting_id'],
|
||||
@conversation.contact
|
||||
)
|
||||
render_response(response)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def render_response(response)
|
||||
render json: response, status: response[:error].blank? ? :ok : :unprocessable_entity
|
||||
end
|
||||
|
||||
def dyte_processor_service
|
||||
Integrations::Dyte::ProcessorService.new(account: @web_widget.inbox.account, conversation: @conversation)
|
||||
end
|
||||
|
||||
def set_message
|
||||
@message = @web_widget.inbox.messages.find(permitted_params[:message_id])
|
||||
@conversation = @message.conversation
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
params.permit(:website_token, :message_id)
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
module MicrosoftConcern
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
def microsoft_client
|
||||
::OAuth2::Client.new(ENV.fetch('AZURE_APP_ID', nil), ENV.fetch('AZURE_APP_SECRET', nil),
|
||||
{
|
||||
site: 'https://login.microsoftonline.com',
|
||||
authorize_url: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
|
||||
token_url: 'https://login.microsoftonline.com/common/oauth2/v2.0/token'
|
||||
})
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def parsed_body
|
||||
@parsed_body ||= Rack::Utils.parse_nested_query(@response.raw_response.body)
|
||||
end
|
||||
|
||||
def base_url
|
||||
ENV.fetch('FRONTEND_URL', 'http://localhost:3000')
|
||||
end
|
||||
end
|
||||
@@ -55,7 +55,8 @@ class DashboardController < ActionController::Base
|
||||
ENABLE_ACCOUNT_SIGNUP: GlobalConfigService.load('ENABLE_ACCOUNT_SIGNUP', 'false'),
|
||||
FB_APP_ID: GlobalConfigService.load('FB_APP_ID', ''),
|
||||
FACEBOOK_API_VERSION: 'v14.0',
|
||||
IS_ENTERPRISE: ChatwootApp.enterprise?
|
||||
IS_ENTERPRISE: ChatwootApp.enterprise?,
|
||||
AZURE_APP_ID: ENV.fetch('AZURE_APP_ID', '')
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
class Microsoft::CallbacksController < ApplicationController
|
||||
include MicrosoftConcern
|
||||
|
||||
def show
|
||||
@response = microsoft_client.auth_code.get_token(
|
||||
oauth_code,
|
||||
redirect_uri: "#{base_url}/microsoft/callback"
|
||||
)
|
||||
|
||||
inbox = find_or_create_inbox
|
||||
::Redis::Alfred.delete(users_data['email'])
|
||||
redirect_to app_microsoft_inbox_agents_url(account_id: account.id, inbox_id: inbox.id)
|
||||
rescue StandardError => e
|
||||
ChatwootExceptionTracker.new(e).capture_exception
|
||||
redirect_to '/'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def oauth_code
|
||||
params[:code]
|
||||
end
|
||||
|
||||
def users_data
|
||||
decoded_token = JWT.decode parsed_body[:id_token], nil, false
|
||||
decoded_token[0]
|
||||
end
|
||||
|
||||
def parsed_body
|
||||
@parsed_body ||= @response.response.parsed
|
||||
end
|
||||
|
||||
def account_id
|
||||
::Redis::Alfred.get(users_data['email'])
|
||||
end
|
||||
|
||||
def account
|
||||
@account ||= Account.find(account_id)
|
||||
end
|
||||
|
||||
def find_or_create_inbox
|
||||
channel_email = Channel::Email.find_by(email: users_data['email'], account: account)
|
||||
channel_email ||= create_microsoft_channel_with_inbox
|
||||
update_microsoft_channel(channel_email)
|
||||
channel_email.inbox
|
||||
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']
|
||||
)
|
||||
channel_email
|
||||
end
|
||||
end
|
||||
|
||||
def update_microsoft_channel(channel_email)
|
||||
channel_email.update!({
|
||||
imap_login: users_data['email'], imap_address: 'outlook.office365.com',
|
||||
imap_port: '993', imap_enabled: true,
|
||||
provider: 'microsoft',
|
||||
provider_config: {
|
||||
access_token: parsed_body['access_token'],
|
||||
refresh_token: parsed_body['refresh_token'],
|
||||
expires_on: (Time.current.utc + 1.hour).to_s
|
||||
}
|
||||
})
|
||||
end
|
||||
end
|
||||
@@ -11,8 +11,8 @@ class SwaggerController < ApplicationController
|
||||
|
||||
def derived_path
|
||||
params[:path] ||= 'index.html'
|
||||
path = params[:path]
|
||||
path << ".#{params[:format]}" unless path.ends_with?(params[:format].to_s)
|
||||
path = Rack::Utils.clean_path_info(params[:path])
|
||||
path << ".#{Rack::Utils.clean_path_info(params[:format])}" unless path.ends_with?(params[:format].to_s)
|
||||
path
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
class ContactDrop < BaseDrop
|
||||
def email
|
||||
@obj.try(:email)
|
||||
end
|
||||
|
||||
def phone_number
|
||||
@obj.try(:phone_number)
|
||||
end
|
||||
|
||||
def first_name
|
||||
@obj.try(:name).try(:split).try(:first)
|
||||
end
|
||||
|
||||
def last_name
|
||||
@obj.try(:name).try(:split).try(:last) if @obj.try(:name).try(:split).try(:size) > 1
|
||||
end
|
||||
end
|
||||
@@ -2,4 +2,12 @@ class UserDrop < BaseDrop
|
||||
def available_name
|
||||
@obj.try(:available_name)
|
||||
end
|
||||
|
||||
def first_name
|
||||
@obj.try(:name).try(:split).try(:first)
|
||||
end
|
||||
|
||||
def last_name
|
||||
@obj.try(:name).try(:split).try(:last) if @obj.try(:name).try(:split).try(:size) > 1
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/* global axios */
|
||||
import ApiClient from '../ApiClient';
|
||||
|
||||
class MicrosoftClient extends ApiClient {
|
||||
constructor() {
|
||||
super('microsoft', { accountScoped: true });
|
||||
}
|
||||
|
||||
generateAuthorization(payload) {
|
||||
return axios.post(`${this.url}/authorization`, payload);
|
||||
}
|
||||
}
|
||||
|
||||
export default new MicrosoftClient();
|
||||
@@ -0,0 +1,23 @@
|
||||
/* global axios */
|
||||
|
||||
import ApiClient from '../ApiClient';
|
||||
|
||||
class DyteAPI extends ApiClient {
|
||||
constructor() {
|
||||
super('integrations/dyte', { accountScoped: true });
|
||||
}
|
||||
|
||||
createAMeeting(conversationId) {
|
||||
return axios.post(`${this.url}/create_a_meeting`, {
|
||||
conversation_id: conversationId,
|
||||
});
|
||||
}
|
||||
|
||||
addParticipantToMeeting(messageId) {
|
||||
return axios.post(`${this.url}/add_participant_to_meeting`, {
|
||||
message_id: messageId,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default new DyteAPI();
|
||||
@@ -0,0 +1,35 @@
|
||||
import DyteAPIClient from '../../integrations/dyte';
|
||||
import ApiClient from '../../ApiClient';
|
||||
import describeWithAPIMock from '../apiSpecHelper';
|
||||
|
||||
describe('#accountAPI', () => {
|
||||
it('creates correct instance', () => {
|
||||
expect(DyteAPIClient).toBeInstanceOf(ApiClient);
|
||||
expect(DyteAPIClient).toHaveProperty('createAMeeting');
|
||||
expect(DyteAPIClient).toHaveProperty('addParticipantToMeeting');
|
||||
});
|
||||
|
||||
describeWithAPIMock('createAMeeting', context => {
|
||||
it('creates a valid request', () => {
|
||||
DyteAPIClient.createAMeeting(1);
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/dyte/create_a_meeting',
|
||||
{
|
||||
conversation_id: 1,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describeWithAPIMock('addParticipantToMeeting', context => {
|
||||
it('creates a valid request', () => {
|
||||
DyteAPIClient.addParticipantToMeeting(1);
|
||||
expect(context.axiosMock.post).toHaveBeenCalledWith(
|
||||
'/api/v1/integrations/dyte/add_participant_to_meeting',
|
||||
{
|
||||
message_id: 1,
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB |
@@ -75,7 +75,7 @@ Arial,
|
||||
sans-serif;
|
||||
$body-antialiased: true;
|
||||
$global-margin: $space-small;
|
||||
$global-padding: $space-micro;
|
||||
$global-padding: $space-small;
|
||||
$global-weight-normal: normal;
|
||||
$global-weight-bold: bold;
|
||||
$global-radius: 0;
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
@include foundation-prototype-sizing;
|
||||
@include foundation-prototype-spacing;
|
||||
|
||||
|
||||
@import 'typography';
|
||||
@import 'layout';
|
||||
@import 'animations';
|
||||
@@ -61,7 +60,6 @@
|
||||
@import 'widgets/woot-tables';
|
||||
|
||||
@import 'views/settings/inbox';
|
||||
@import 'views/settings/channel';
|
||||
@import 'views/settings/integrations';
|
||||
|
||||
@import 'plugins/multiselect';
|
||||
|
||||
@@ -1,52 +0,0 @@
|
||||
$channel-hover-color: rgba(0, 0, 0, 0.1);
|
||||
|
||||
.channels {
|
||||
margin-top: $space-medium;
|
||||
|
||||
.inactive {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
.channel {
|
||||
@include flex;
|
||||
@include background-white;
|
||||
@include border-light;
|
||||
|
||||
cursor: pointer;
|
||||
flex-direction: column;
|
||||
margin: -1px;
|
||||
padding: $space-normal $zero;
|
||||
transition: all 0.2s ease-in;
|
||||
|
||||
&:last-child {
|
||||
@include border-light;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border: 1px solid $primary-color;
|
||||
box-shadow: 0 2px 8px $channel-hover-color;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
img {
|
||||
margin: $space-normal auto;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.channel__title {
|
||||
color: $color-body;
|
||||
font-size: var(--font-size-default);
|
||||
text-align: center;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
p {
|
||||
color: $medium-gray;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,6 @@ $resolve-button-width: 13.2rem;
|
||||
margin-right: var(--space-normal);
|
||||
min-width: 0;
|
||||
|
||||
|
||||
.user--profile__meta {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
@@ -54,13 +53,17 @@ $resolve-button-width: 13.2rem;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.header-actions-wrap {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-grow: 1;
|
||||
justify-content: flex-end;
|
||||
margin-top: var(--space-small);
|
||||
|
||||
@include breakpoint(medium up) {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&.has-open-sidebar {
|
||||
justify-content: flex-end;
|
||||
|
||||
@@ -81,8 +81,12 @@
|
||||
}
|
||||
|
||||
.conversations-list {
|
||||
@include scroll-on-hover;
|
||||
overflow-y: auto;
|
||||
flex: 1 1;
|
||||
|
||||
@include breakpoint(large up) {
|
||||
@include scroll-on-hover;
|
||||
}
|
||||
}
|
||||
|
||||
.chat-list__top {
|
||||
@@ -324,6 +328,7 @@
|
||||
var(--space-one);
|
||||
|
||||
.is-text {
|
||||
align-items: center;
|
||||
display: inline-flex;
|
||||
text-align: start;
|
||||
|
||||
|
||||
@@ -82,8 +82,8 @@
|
||||
@include flex-align($x: flex-end, $y: middle);
|
||||
padding: $space-small $zero;
|
||||
|
||||
button {
|
||||
font-size: $font-size-small;
|
||||
.button {
|
||||
margin-left: var(--space-small);
|
||||
}
|
||||
|
||||
&.justify-content-end {
|
||||
|
||||
@@ -59,12 +59,8 @@
|
||||
|
||||
.hamburger--menu {
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
display: block;
|
||||
margin-right: $space-normal;
|
||||
|
||||
@media screen and (max-width: 1200px) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.header--icon {
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<button class="small-6 medium-4 large-3 channel" @click="$emit('click')">
|
||||
<img :src="src" :alt="title" />
|
||||
<h3 class="channel__title">
|
||||
{{ title }}
|
||||
</h3>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
src: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.inactive {
|
||||
filter: grayscale(100%);
|
||||
}
|
||||
|
||||
.channel {
|
||||
background: var(--white);
|
||||
border: 1px solid var(--color-border-light);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin: -1px;
|
||||
padding: var(--space-normal) 0;
|
||||
transition: all 0.2s ease-in;
|
||||
align-items: center;
|
||||
&:hover {
|
||||
border: 1px solid var(--w-500);
|
||||
box-shadow: var(--shadow-medium);
|
||||
z-index: var(--z-index-high);
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
img {
|
||||
margin: var(--space-normal) auto;
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
.channel__title {
|
||||
color: var(--color-body);
|
||||
font-size: var(--font-size-default);
|
||||
text-align: center;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
p {
|
||||
color: var(--b-500);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -794,30 +794,15 @@ export default {
|
||||
|
||||
.conversations-list-wrap {
|
||||
flex-shrink: 0;
|
||||
width: 34rem;
|
||||
flex-basis: clamp(32rem, 4vw + 34rem, 44rem);
|
||||
overflow: hidden;
|
||||
@include breakpoint(large up) {
|
||||
width: 36rem;
|
||||
}
|
||||
@include breakpoint(xlarge up) {
|
||||
width: 35rem;
|
||||
}
|
||||
@include breakpoint(xxlarge up) {
|
||||
width: 38rem;
|
||||
}
|
||||
@include breakpoint(xxxlarge up) {
|
||||
flex-basis: 46rem;
|
||||
}
|
||||
|
||||
&.hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&.list--full-width {
|
||||
width: 100%;
|
||||
@include breakpoint(xxxlarge up) {
|
||||
flex-basis: 100%;
|
||||
}
|
||||
flex-basis: 100%;
|
||||
}
|
||||
}
|
||||
.filter--actions {
|
||||
|
||||
@@ -39,6 +39,8 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
.wrap-content {
|
||||
word-wrap: break-word;
|
||||
margin-top: var(--space-small);
|
||||
|
||||
.content-value {
|
||||
font-weight: var(--font-weight-bold);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<template>
|
||||
<button @click="onMenuItemClick">
|
||||
<fluent-icon class="hamburger--menu" icon="list" />
|
||||
</button>
|
||||
<woot-button
|
||||
size="small"
|
||||
variant="clear"
|
||||
color-scheme="secondary"
|
||||
icon="list"
|
||||
class="toggle-sidebar"
|
||||
@click="onMenuItemClick"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -16,13 +21,8 @@ export default {
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="scss">
|
||||
.hamburger--menu {
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
margin-right: var(--space-normal);
|
||||
|
||||
@media screen and (max-width: 1200px) {
|
||||
display: block;
|
||||
}
|
||||
.toggle-sidebar {
|
||||
margin-right: var(--space-small);
|
||||
margin-left: var(--space-minus-small);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -233,9 +233,6 @@ export default {
|
||||
width: 40rem;
|
||||
}
|
||||
}
|
||||
.off-canvas-content.is-open-left {
|
||||
transform: translateX(18.8rem);
|
||||
}
|
||||
|
||||
.secondary-sidebar {
|
||||
overflow-y: auto;
|
||||
|
||||
@@ -261,14 +261,7 @@ export default {
|
||||
width: 20rem;
|
||||
flex-shrink: 0;
|
||||
overflow-y: hidden;
|
||||
|
||||
@include breakpoint(xlarge down) {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
@include breakpoint(xlarge up) {
|
||||
position: unset;
|
||||
}
|
||||
position: unset;
|
||||
|
||||
&:hover {
|
||||
overflow-y: hidden;
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`SidemenuIcon matches snapshot 1`] = `
|
||||
<button>
|
||||
<fluent-icon
|
||||
class="hamburger--menu"
|
||||
icon="list"
|
||||
/>
|
||||
</button>
|
||||
<woot-button
|
||||
class="toggle-sidebar"
|
||||
color-scheme="secondary"
|
||||
icon="list"
|
||||
size="small"
|
||||
variant="clear"
|
||||
/>
|
||||
`;
|
||||
|
||||
@@ -88,9 +88,9 @@
|
||||
v-model="action_params"
|
||||
:teams="dropdownValues"
|
||||
/>
|
||||
<textarea
|
||||
<woot-message-editor
|
||||
v-if="inputType === 'textarea'"
|
||||
v-model="action_params"
|
||||
v-model="castMessageVmodel"
|
||||
rows="4"
|
||||
:placeholder="$t('AUTOMATION.ACTION.TEAM_MESSAGE_INPUT_PLACEHOLDER')"
|
||||
class="action-message"
|
||||
@@ -107,10 +107,12 @@
|
||||
<script>
|
||||
import AutomationActionTeamMessageInput from './AutomationActionTeamMessageInput.vue';
|
||||
import AutomationActionFileInput from './AutomationFileInput.vue';
|
||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor';
|
||||
export default {
|
||||
components: {
|
||||
AutomationActionTeamMessageInput,
|
||||
AutomationActionFileInput,
|
||||
WootMessageEditor,
|
||||
},
|
||||
props: {
|
||||
value: {
|
||||
@@ -173,6 +175,17 @@ export default {
|
||||
'is-a-macro': this.isMacro,
|
||||
};
|
||||
},
|
||||
castMessageVmodel: {
|
||||
get() {
|
||||
if (Array.isArray(this.action_params)) {
|
||||
return this.action_params[0];
|
||||
}
|
||||
return this.action_params;
|
||||
},
|
||||
set(value) {
|
||||
this.action_params = value;
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
removeAction() {
|
||||
@@ -281,4 +294,8 @@ export default {
|
||||
.action-message {
|
||||
margin: var(--space-small) var(--space-zero) var(--space-zero);
|
||||
}
|
||||
// Prosemirror does not have a native way of hiding the menu bar, hence
|
||||
::v-deep .ProseMirror-menubar {
|
||||
display: none;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,56 +1,15 @@
|
||||
<template>
|
||||
<div
|
||||
class="small-6 medium-4 large-3 columns channel"
|
||||
<channel-selector
|
||||
:class="{ inactive: !isActive }"
|
||||
:title="channel.name"
|
||||
:src="getChannelThumbnail()"
|
||||
@click="onItemClick"
|
||||
>
|
||||
<img
|
||||
v-if="channel.key === 'facebook'"
|
||||
src="~dashboard/assets/images/channels/messenger.png"
|
||||
/>
|
||||
<img
|
||||
v-if="channel.key === 'twitter'"
|
||||
src="~dashboard/assets/images/channels/twitter.png"
|
||||
/>
|
||||
<img
|
||||
v-if="channel.key === 'telegram'"
|
||||
src="~dashboard/assets/images/channels/telegram.png"
|
||||
/>
|
||||
<img
|
||||
v-if="channel.key === 'api' && !channel.thumbnail"
|
||||
src="~dashboard/assets/images/channels/api.png"
|
||||
/>
|
||||
<img
|
||||
v-if="channel.key === 'api' && channel.thumbnail"
|
||||
:src="channel.thumbnail"
|
||||
/>
|
||||
<img
|
||||
v-if="channel.key === 'email'"
|
||||
src="~dashboard/assets/images/channels/email.png"
|
||||
/>
|
||||
<img
|
||||
v-if="channel.key === 'line'"
|
||||
src="~dashboard/assets/images/channels/line.png"
|
||||
/>
|
||||
<img
|
||||
v-if="channel.key === 'website'"
|
||||
src="~dashboard/assets/images/channels/website.png"
|
||||
/>
|
||||
<img
|
||||
v-if="channel.key === 'sms'"
|
||||
src="~dashboard/assets/images/channels/sms.png"
|
||||
/>
|
||||
<img
|
||||
v-if="channel.key === 'whatsapp'"
|
||||
src="~dashboard/assets/images/channels/whatsapp.png"
|
||||
/>
|
||||
<h3 class="channel__title">
|
||||
{{ channel.name }}
|
||||
</h3>
|
||||
</div>
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
import ChannelSelector from '../ChannelSelector';
|
||||
export default {
|
||||
components: { ChannelSelector },
|
||||
props: {
|
||||
channel: {
|
||||
type: Object,
|
||||
@@ -92,6 +51,12 @@ export default {
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getChannelThumbnail() {
|
||||
if (this.channel.key === 'api' && this.channel.thumbnail) {
|
||||
return this.channel.thumbnail;
|
||||
}
|
||||
return `/assets/images/dashboard/channels/${this.channel.key}.png`;
|
||||
},
|
||||
onItemClick() {
|
||||
if (this.isActive) {
|
||||
this.$emit('channel-item-click', this.channel.key);
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<woot-button
|
||||
v-if="isVideoIntegrationEnabled"
|
||||
v-tooltip.top-end="
|
||||
$t('INTEGRATION_SETTINGS.DYTE.START_VIDEO_CALL_HELP_TEXT')
|
||||
"
|
||||
icon="video"
|
||||
:is-loading="isLoading"
|
||||
color-scheme="secondary"
|
||||
variant="smooth"
|
||||
size="small"
|
||||
@click="onClick"
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import DyteAPI from 'dashboard/api/integrations/dyte';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
|
||||
export default {
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
conversationId: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return { isLoading: false };
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({ appIntegrations: 'integrations/getAppIntegrations' }),
|
||||
isVideoIntegrationEnabled() {
|
||||
return this.appIntegrations.find(
|
||||
integration => integration.id === 'dyte' && !!integration.hooks.length
|
||||
);
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
if (!this.appIntegrations.length) {
|
||||
this.$store.dispatch('integrations/get');
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async onClick() {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
await DyteAPI.createAMeeting(this.conversationId);
|
||||
} catch (error) {
|
||||
this.showAlert(this.$t('INTEGRATION_SETTINGS.DYTE.CREATE_ERROR'));
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -10,11 +10,11 @@ import 'videojs-record/dist/css/videojs.record.css';
|
||||
|
||||
import videojs from 'video.js';
|
||||
|
||||
import inboxMixin from '../../../../shared/mixins/inboxMixin';
|
||||
import alertMixin from '../../../../shared/mixins/alertMixin';
|
||||
|
||||
import Recorder from 'opus-recorder';
|
||||
import encoderWorker from 'opus-recorder/dist/encoderWorker.min';
|
||||
import waveWorker from 'opus-recorder/dist/waveWorker.min';
|
||||
|
||||
import WaveSurfer from 'wavesurfer.js';
|
||||
import MicrophonePlugin from 'wavesurfer.js/dist/plugin/wavesurfer.microphone.js';
|
||||
@@ -29,14 +29,19 @@ WaveSurfer.microphone = MicrophonePlugin;
|
||||
|
||||
export default {
|
||||
name: 'WootAudioRecorder',
|
||||
mixins: [inboxMixin, alertMixin],
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
audioRecordFormat: {
|
||||
type: String,
|
||||
default: AUDIO_FORMATS.WEBM,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
player: false,
|
||||
recordingDateStarted: new Date(0),
|
||||
initialTimeDuration: '00:00',
|
||||
recorderOptions: {
|
||||
debug: true,
|
||||
controls: true,
|
||||
bigPlayButton: false,
|
||||
fluid: false,
|
||||
@@ -71,6 +76,9 @@ export default {
|
||||
record: {
|
||||
audio: true,
|
||||
video: false,
|
||||
maxLength: 900,
|
||||
timeSlice: 1000,
|
||||
maxFileSize: 15 * 1024 * 1024,
|
||||
...(this.audioRecordFormat === AUDIO_FORMATS.WEBM && {
|
||||
monitorGain: 0,
|
||||
recordingGain: 1,
|
||||
@@ -80,11 +88,10 @@ export default {
|
||||
streamPages: true,
|
||||
maxFramesPerPage: 1,
|
||||
encoderFrameSize: 1,
|
||||
encoderPath: 'opus-recorder/dist/waveWorker.min.js',
|
||||
encoderPath: waveWorker,
|
||||
}),
|
||||
...(this.audioRecordFormat === AUDIO_FORMATS.OGG && {
|
||||
displayMilliseconds: false,
|
||||
maxLength: 300,
|
||||
audioEngine: 'opus-recorder',
|
||||
audioWorkerURL: encoderWorker,
|
||||
audioChannels: 1,
|
||||
@@ -100,12 +107,6 @@ export default {
|
||||
isRecording() {
|
||||
return this.player && this.player.record().isRecording();
|
||||
},
|
||||
audioRecordFormat() {
|
||||
if (this.isAWebWidgetInbox) {
|
||||
return AUDIO_FORMATS.WEBM;
|
||||
}
|
||||
return AUDIO_FORMATS.OGG;
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
window.Recorder = Recorder;
|
||||
|
||||
@@ -15,29 +15,25 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { EditorView } from 'prosemirror-view';
|
||||
|
||||
import { defaultMarkdownSerializer } from 'prosemirror-markdown';
|
||||
import {
|
||||
addMentionsToMarkdownSerializer,
|
||||
addMentionsToMarkdownParser,
|
||||
schemaWithMentions,
|
||||
} from '@chatwoot/prosemirror-schema/src/mentions/schema';
|
||||
|
||||
messageSchema,
|
||||
wootMessageWriterSetup,
|
||||
EditorView,
|
||||
MessageMarkdownTransformer,
|
||||
MessageMarkdownSerializer,
|
||||
EditorState,
|
||||
Selection,
|
||||
} from '@chatwoot/prosemirror-schema';
|
||||
import {
|
||||
suggestionsPlugin,
|
||||
triggerCharacters,
|
||||
} from '@chatwoot/prosemirror-schema/src/mentions/plugin';
|
||||
import { EditorState, Selection } from 'prosemirror-state';
|
||||
import { defaultMarkdownParser } from 'prosemirror-markdown';
|
||||
import { wootWriterSetup } from '@chatwoot/prosemirror-schema';
|
||||
|
||||
import TagAgents from '../conversation/TagAgents';
|
||||
import CannedResponse from '../conversation/CannedResponse';
|
||||
|
||||
const TYPING_INDICATOR_IDLE_TIME = 4000;
|
||||
|
||||
import '@chatwoot/prosemirror-schema/src/woot-editor.css';
|
||||
import {
|
||||
hasPressedEnterAndNotCmdOrShift,
|
||||
hasPressedCommandAndEnter,
|
||||
@@ -53,9 +49,9 @@ import AnalyticsHelper, {
|
||||
|
||||
const createState = (content, placeholder, plugins = []) => {
|
||||
return EditorState.create({
|
||||
doc: addMentionsToMarkdownParser(defaultMarkdownParser).parse(content),
|
||||
plugins: wootWriterSetup({
|
||||
schema: schemaWithMentions,
|
||||
doc: new MessageMarkdownTransformer(messageSchema).parse(content),
|
||||
plugins: wootMessageWriterSetup({
|
||||
schema: messageSchema,
|
||||
placeholder,
|
||||
plugins,
|
||||
}),
|
||||
@@ -88,9 +84,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
contentFromEditor() {
|
||||
return addMentionsToMarkdownSerializer(
|
||||
defaultMarkdownSerializer
|
||||
).serialize(this.editorView.state.doc);
|
||||
return MessageMarkdownSerializer.serialize(this.editorView.state.doc);
|
||||
},
|
||||
plugins() {
|
||||
if (!this.enableSuggestions) {
|
||||
@@ -282,11 +276,11 @@ export default {
|
||||
}
|
||||
|
||||
let from = this.range.from - 1;
|
||||
let node = addMentionsToMarkdownParser(defaultMarkdownParser).parse(
|
||||
let node = new MessageMarkdownTransformer(messageSchema).parse(
|
||||
cannedItem
|
||||
);
|
||||
|
||||
if (node.childCount === 1) {
|
||||
if (node.textContent === cannedItem) {
|
||||
node = this.editorView.state.schema.text(cannedItem);
|
||||
from = this.range.from;
|
||||
}
|
||||
@@ -372,10 +366,18 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@chatwoot/prosemirror-schema/src/styles/base.scss';
|
||||
|
||||
.ProseMirror-menubar-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.ProseMirror-menubar {
|
||||
min-height: var(--space-two) !important;
|
||||
margin-left: var(--space-minus-one);
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
> .ProseMirror {
|
||||
padding: 0;
|
||||
word-break: break-word;
|
||||
@@ -384,6 +386,7 @@ export default {
|
||||
|
||||
.editor-root {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ProseMirror-woot-style {
|
||||
@@ -406,6 +409,9 @@ export default {
|
||||
color: var(--s-900);
|
||||
padding: 0 var(--space-smaller);
|
||||
}
|
||||
.ProseMirror-menubar {
|
||||
background: var(--y-50);
|
||||
}
|
||||
}
|
||||
|
||||
.editor-wrap {
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="editor-root editor--article">
|
||||
<div ref="editor" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
fullSchema,
|
||||
wootArticleWriterSetup,
|
||||
EditorView,
|
||||
ArticleMarkdownSerializer,
|
||||
ArticleMarkdownTransformer,
|
||||
EditorState,
|
||||
Selection,
|
||||
} from '@chatwoot/prosemirror-schema';
|
||||
|
||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||
|
||||
const createState = (content, placeholder, plugins = []) => {
|
||||
return EditorState.create({
|
||||
doc: new ArticleMarkdownTransformer(fullSchema).parse(content),
|
||||
plugins: wootArticleWriterSetup({
|
||||
schema: fullSchema,
|
||||
placeholder,
|
||||
plugins,
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
||||
export default {
|
||||
mixins: [eventListenerMixins, uiSettingsMixin],
|
||||
props: {
|
||||
value: { type: String, default: '' },
|
||||
editorId: { type: String, default: '' },
|
||||
placeholder: { type: String, default: '' },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
editorView: null,
|
||||
state: undefined,
|
||||
plugins: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
contentFromEditor() {
|
||||
if (this.editorView) {
|
||||
return ArticleMarkdownSerializer.serialize(this.editorView.state.doc);
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
value(newValue = '') {
|
||||
if (newValue !== this.contentFromEditor) {
|
||||
this.reloadState();
|
||||
}
|
||||
},
|
||||
editorId() {
|
||||
this.reloadState();
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.state = createState(this.value, this.placeholder, this.plugins);
|
||||
},
|
||||
mounted() {
|
||||
this.createEditorView();
|
||||
|
||||
this.editorView.updateState(this.state);
|
||||
this.focusEditorInputField();
|
||||
},
|
||||
methods: {
|
||||
reloadState() {
|
||||
this.state = createState(this.value, this.placeholder, this.plugins);
|
||||
this.editorView.updateState(this.state);
|
||||
this.focusEditorInputField();
|
||||
},
|
||||
createEditorView() {
|
||||
this.editorView = new EditorView(this.$refs.editor, {
|
||||
state: this.state,
|
||||
dispatchTransaction: tx => {
|
||||
this.state = this.state.apply(tx);
|
||||
this.emitOnChange();
|
||||
},
|
||||
handleDOMEvents: {
|
||||
keyup: () => {
|
||||
this.onKeyup();
|
||||
},
|
||||
keydown: (view, event) => {
|
||||
this.onKeydown(event);
|
||||
},
|
||||
focus: () => {
|
||||
this.onFocus();
|
||||
},
|
||||
blur: () => {
|
||||
this.onBlur();
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
handleKeyEvents() {},
|
||||
focusEditorInputField() {
|
||||
const { tr } = this.editorView.state;
|
||||
const selection = Selection.atEnd(tr.doc);
|
||||
|
||||
this.editorView.dispatch(tr.setSelection(selection));
|
||||
this.editorView.focus();
|
||||
},
|
||||
|
||||
emitOnChange() {
|
||||
this.editorView.updateState(this.state);
|
||||
|
||||
this.$emit('input', this.contentFromEditor);
|
||||
},
|
||||
|
||||
onKeyup() {
|
||||
this.$emit('keyup');
|
||||
},
|
||||
onKeydown() {
|
||||
this.$emit('keydown');
|
||||
},
|
||||
onBlur() {
|
||||
this.$emit('blur');
|
||||
},
|
||||
onFocus() {
|
||||
this.$emit('focus');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~@chatwoot/prosemirror-schema/src/styles/article.scss';
|
||||
|
||||
.ProseMirror-menubar-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
> .ProseMirror {
|
||||
padding: 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
}
|
||||
|
||||
.editor-root {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.ProseMirror-woot-style {
|
||||
min-height: 8rem;
|
||||
max-height: 12rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.ProseMirror-prompt {
|
||||
z-index: var(--z-index-highest);
|
||||
background: var(--white);
|
||||
box-shadow: var(--shadow-large);
|
||||
border-radius: var(--border-radius-normal);
|
||||
border: 1px solid var(--color-border);
|
||||
min-width: 40rem;
|
||||
}
|
||||
</style>
|
||||
@@ -87,6 +87,10 @@
|
||||
:title="'Whatsapp Templates'"
|
||||
@click="$emit('selectWhatsappTemplate')"
|
||||
/>
|
||||
<video-call-button
|
||||
v-if="(isAWebWidgetInbox || isAPIInbox) && !isOnPrivateNote"
|
||||
:conversation-id="conversationId"
|
||||
/>
|
||||
<transition name="modal-fade">
|
||||
<div
|
||||
v-show="$refs.upload && $refs.upload.dropActive"
|
||||
@@ -124,13 +128,13 @@ import {
|
||||
ALLOWED_FILE_TYPES,
|
||||
ALLOWED_FILE_TYPES_FOR_TWILIO_WHATSAPP,
|
||||
} from 'shared/constants/messages';
|
||||
|
||||
import VideoCallButton from '../VideoCallButton';
|
||||
import { REPLY_EDITOR_MODES } from './constants';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
name: 'ReplyBottomPanel',
|
||||
components: { FileUpload },
|
||||
components: { FileUpload, VideoCallButton },
|
||||
mixins: [eventListenerMixins, uiSettingsMixin, inboxMixin],
|
||||
props: {
|
||||
mode: {
|
||||
@@ -209,6 +213,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
conversationId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
@@ -269,7 +277,7 @@ export default {
|
||||
}
|
||||
},
|
||||
showMessageSignatureButton() {
|
||||
return !this.isPrivate && this.isAnEmailChannel;
|
||||
return !this.isOnPrivateNote && this.isAnEmailChannel;
|
||||
},
|
||||
sendWithSignature() {
|
||||
const { send_with_signature: isEnabled } = this.uiSettings;
|
||||
|
||||
@@ -166,10 +166,14 @@ export default {
|
||||
.conversation-sidebar-wrap {
|
||||
height: auto;
|
||||
flex: 0 0;
|
||||
overflow: hidden;
|
||||
z-index: var(--z-index-low);
|
||||
overflow: auto;
|
||||
background: white;
|
||||
flex-basis: 28rem;
|
||||
flex-basis: 100%;
|
||||
|
||||
@include breakpoint(medium up) {
|
||||
flex-basis: 28rem;
|
||||
}
|
||||
|
||||
@include breakpoint(large up) {
|
||||
flex-basis: 30em;
|
||||
|
||||
@@ -1,49 +1,54 @@
|
||||
<template>
|
||||
<div class="conv-header">
|
||||
<div class="user">
|
||||
<back-button v-if="showBackButton" :back-url="backButtonUrl" />
|
||||
<Thumbnail
|
||||
:src="currentContact.thumbnail"
|
||||
size="40px"
|
||||
:badge="inboxBadge"
|
||||
:username="currentContact.name"
|
||||
: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>
|
||||
<div class="conversation--header--actions">
|
||||
<inbox-name :inbox="inbox" class="margin-right-small" />
|
||||
<span
|
||||
v-if="isSnoozed"
|
||||
class="snoozed--display-text margin-right-small"
|
||||
>
|
||||
{{ snoozedDisplayText }}
|
||||
</span>
|
||||
<woot-button
|
||||
class="user--profile__button margin-right-small"
|
||||
size="small"
|
||||
variant="link"
|
||||
@click="$emit('contact-panel-toggle')"
|
||||
>
|
||||
{{ contactPanelToggleText }}
|
||||
</woot-button>
|
||||
<div class="conversation-header--details">
|
||||
<div class="user">
|
||||
<back-button v-if="showBackButton" :back-url="backButtonUrl" />
|
||||
<Thumbnail
|
||||
:src="currentContact.thumbnail"
|
||||
:badge="inboxBadge"
|
||||
:username="currentContact.name"
|
||||
: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>
|
||||
<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"
|
||||
>
|
||||
{{ snoozedDisplayText }}
|
||||
</span>
|
||||
<woot-button
|
||||
class="user--profile__button margin-right-small"
|
||||
size="small"
|
||||
variant="link"
|
||||
@click="$emit('contact-panel-toggle')"
|
||||
>
|
||||
{{ contactPanelToggleText }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="header-actions-wrap"
|
||||
:class="{ 'has-open-sidebar': isContactPanelOpen }"
|
||||
>
|
||||
<more-actions :conversation-id="currentChat.id" />
|
||||
<div
|
||||
class="header-actions-wrap"
|
||||
:class="{ 'has-open-sidebar': isContactPanelOpen }"
|
||||
>
|
||||
<more-actions :conversation-id="currentChat.id" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -145,6 +150,9 @@ export default {
|
||||
const { inbox_id: inboxId } = this.chat;
|
||||
return this.$store.getters['inboxes/getInbox'](inboxId);
|
||||
},
|
||||
hasMultipleInboxes() {
|
||||
return this.$store.getters['inboxes/getInboxes'].length > 1;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
@@ -158,14 +166,27 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.text-truncate {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
@import '~dashboard/assets/scss/woot';
|
||||
|
||||
.conv-header {
|
||||
flex: 0 0 var(--space-jumbo);
|
||||
flex-direction: row;
|
||||
|
||||
@include breakpoint(medium up) {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.conversation-header--details {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
@include breakpoint(medium up) {
|
||||
flex-direction: row;
|
||||
}
|
||||
}
|
||||
|
||||
.option__desc {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<template>
|
||||
<li
|
||||
v-if="hasAttachments || data.content || isEmailContentType"
|
||||
:class="alignBubble"
|
||||
>
|
||||
<li v-if="shouldRenderMessage" :class="alignBubble">
|
||||
<div :class="wrapClass">
|
||||
<div v-tooltip.top-start="messageToolTip" :class="bubbleClass">
|
||||
<bubble-mail-head
|
||||
@@ -15,9 +12,13 @@
|
||||
v-if="data.content"
|
||||
:message="message"
|
||||
:is-email="isEmailContentType"
|
||||
:readable-time="readableTime"
|
||||
:display-quoted-button="displayQuotedButton"
|
||||
/>
|
||||
<bubble-integration
|
||||
:message-id="data.id"
|
||||
:content-attributes="contentAttributes"
|
||||
:inbox-id="data.inbox_id"
|
||||
/>
|
||||
<span
|
||||
v-if="isPending && hasAttachments"
|
||||
class="chat-bubble has-attachment agent"
|
||||
@@ -29,7 +30,6 @@
|
||||
<bubble-image
|
||||
v-if="attachment.file_type === 'image' && !hasImageError"
|
||||
:url="attachment.data_url"
|
||||
:readable-time="readableTime"
|
||||
@error="onImageLoadError"
|
||||
/>
|
||||
<audio v-else-if="attachment.file_type === 'audio'" controls>
|
||||
@@ -38,7 +38,6 @@
|
||||
<bubble-video
|
||||
v-else-if="attachment.file_type === 'video'"
|
||||
:url="attachment.data_url"
|
||||
:readable-time="readableTime"
|
||||
/>
|
||||
<bubble-location
|
||||
v-else-if="attachment.file_type === 'location'"
|
||||
@@ -46,11 +45,7 @@
|
||||
:longitude="attachment.coordinates_long"
|
||||
:name="attachment.fallback_title"
|
||||
/>
|
||||
<bubble-file
|
||||
v-else
|
||||
:url="attachment.data_url"
|
||||
:readable-time="readableTime"
|
||||
/>
|
||||
<bubble-file v-else :url="attachment.data_url" />
|
||||
</div>
|
||||
</div>
|
||||
<bubble-actions
|
||||
@@ -65,10 +60,9 @@
|
||||
:is-private="data.private"
|
||||
:message-type="data.message_type"
|
||||
:message-status="status"
|
||||
:readable-time="readableTime"
|
||||
:source-id="data.source_id"
|
||||
:inbox-id="data.inbox_id"
|
||||
:message-read="showReadTicks"
|
||||
:created-at="createdAt"
|
||||
/>
|
||||
</div>
|
||||
<spinner v-if="isPending" size="tiny" />
|
||||
@@ -119,16 +113,14 @@
|
||||
</template>
|
||||
<script>
|
||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||
import timeMixin from '../../../mixins/time';
|
||||
|
||||
import BubbleActions from './bubble/Actions';
|
||||
import BubbleFile from './bubble/File';
|
||||
import BubbleImage from './bubble/Image';
|
||||
import BubbleIntegration from './bubble/Integration.vue';
|
||||
import BubbleLocation from './bubble/Location';
|
||||
import BubbleMailHead from './bubble/MailHead';
|
||||
import BubbleText from './bubble/Text';
|
||||
import BubbleImage from './bubble/Image';
|
||||
import BubbleFile from './bubble/File';
|
||||
import BubbleVideo from './bubble/Video.vue';
|
||||
import BubbleActions from './bubble/Actions';
|
||||
import BubbleLocation from './bubble/Location';
|
||||
|
||||
import Spinner from 'shared/components/Spinner';
|
||||
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu';
|
||||
|
||||
@@ -140,16 +132,17 @@ import { generateBotMessageContent } from './helpers/botMessageContentHelper';
|
||||
export default {
|
||||
components: {
|
||||
BubbleActions,
|
||||
BubbleText,
|
||||
BubbleImage,
|
||||
BubbleFile,
|
||||
BubbleVideo,
|
||||
BubbleMailHead,
|
||||
BubbleImage,
|
||||
BubbleIntegration,
|
||||
BubbleLocation,
|
||||
BubbleMailHead,
|
||||
BubbleText,
|
||||
BubbleVideo,
|
||||
ContextMenu,
|
||||
Spinner,
|
||||
},
|
||||
mixins: [alertMixin, timeMixin, messageFormatterMixin, contentTypeMixin],
|
||||
mixins: [alertMixin, messageFormatterMixin, contentTypeMixin],
|
||||
props: {
|
||||
data: {
|
||||
type: Object,
|
||||
@@ -167,10 +160,6 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
hasUserReadMessage: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
isWebWidgetInbox: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
@@ -183,6 +172,14 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
shouldRenderMessage() {
|
||||
return (
|
||||
this.hasAttachments ||
|
||||
this.data.content ||
|
||||
this.isEmailContentType ||
|
||||
this.isAnIntegrationMessage
|
||||
);
|
||||
},
|
||||
emailMessageContent() {
|
||||
const {
|
||||
html_content: { full: fullHTMLContent } = {},
|
||||
@@ -273,11 +270,8 @@ export default {
|
||||
'has-tweet-menu': this.isATweet,
|
||||
};
|
||||
},
|
||||
readableTime() {
|
||||
return this.messageStamp(
|
||||
this.contentAttributes.external_created_at || this.data.created_at,
|
||||
'LLL d, h:mm a'
|
||||
);
|
||||
createdAt() {
|
||||
return this.contentAttributes.external_created_at || this.data.created_at;
|
||||
},
|
||||
isBubble() {
|
||||
return [0, 1, 3].includes(this.data.message_type);
|
||||
@@ -288,17 +282,12 @@ export default {
|
||||
isOutgoing() {
|
||||
return this.data.message_type === MESSAGE_TYPE.OUTGOING;
|
||||
},
|
||||
showReadTicks() {
|
||||
return (
|
||||
(this.isOutgoing || this.isTemplate) &&
|
||||
this.hasUserReadMessage &&
|
||||
this.isWebWidgetInbox &&
|
||||
!this.data.private
|
||||
);
|
||||
},
|
||||
isTemplate() {
|
||||
return this.data.message_type === MESSAGE_TYPE.TEMPLATE;
|
||||
},
|
||||
isAnIntegrationMessage() {
|
||||
return this.contentType === 'integrations';
|
||||
},
|
||||
emailHeadAttributes() {
|
||||
return {
|
||||
email: this.contentAttributes.email,
|
||||
|
||||
@@ -40,9 +40,6 @@
|
||||
:is-a-tweet="isATweet"
|
||||
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
||||
:has-instagram-story="hasInstagramStory"
|
||||
:has-user-read-message="
|
||||
hasUserReadMessage(message.created_at, getLastSeenAt)
|
||||
"
|
||||
:is-web-widget-inbox="isAWebWidgetInbox"
|
||||
/>
|
||||
<li v-show="unreadMessageCount != 0" class="unread--toast">
|
||||
@@ -63,9 +60,6 @@
|
||||
:is-a-tweet="isATweet"
|
||||
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
||||
:has-instagram-story="hasInstagramStory"
|
||||
:has-user-read-message="
|
||||
hasUserReadMessage(message.created_at, getLastSeenAt)
|
||||
"
|
||||
:is-web-widget-inbox="isAWebWidgetInbox"
|
||||
/>
|
||||
</ul>
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
<woot-audio-recorder
|
||||
v-if="showAudioRecorderEditor"
|
||||
ref="audioRecorderInput"
|
||||
:audio-record-format="audioRecordFormat"
|
||||
@state-recorder-progress-changed="onStateProgressRecorderChanged"
|
||||
@state-recorder-changed="onStateRecorderChanged"
|
||||
@finish-record="onFinishRecorder"
|
||||
@@ -95,25 +96,26 @@
|
||||
</p>
|
||||
</div>
|
||||
<reply-bottom-panel
|
||||
:mode="replyType"
|
||||
:inbox="inbox"
|
||||
:send-button-text="replyButtonLabel"
|
||||
:on-file-upload="onFileUpload"
|
||||
:show-file-upload="showFileUpload"
|
||||
:show-audio-recorder="showAudioRecorder"
|
||||
:toggle-emoji-picker="toggleEmojiPicker"
|
||||
:toggle-audio-recorder="toggleAudioRecorder"
|
||||
:toggle-audio-recorder-play-pause="toggleAudioRecorderPlayPause"
|
||||
:show-emoji-picker="showEmojiPicker"
|
||||
:on-send="onSendReply"
|
||||
:is-send-disabled="isReplyButtonDisabled"
|
||||
:recording-audio-duration-text="recordingAudioDurationText"
|
||||
:recording-audio-state="recordingAudioState"
|
||||
:is-recording-audio="isRecordingAudio"
|
||||
:is-on-private-note="isOnPrivateNote"
|
||||
:show-editor-toggle="isAPIInbox && !isOnPrivateNote"
|
||||
:conversation-id="conversationId"
|
||||
:enable-multiple-file-upload="enableMultipleFileUpload"
|
||||
:has-whatsapp-templates="hasWhatsappTemplates"
|
||||
:inbox="inbox"
|
||||
:is-on-private-note="isOnPrivateNote"
|
||||
:is-recording-audio="isRecordingAudio"
|
||||
:is-send-disabled="isReplyButtonDisabled"
|
||||
:mode="replyType"
|
||||
:on-file-upload="onFileUpload"
|
||||
:on-send="onSendReply"
|
||||
:recording-audio-duration-text="recordingAudioDurationText"
|
||||
:recording-audio-state="recordingAudioState"
|
||||
:send-button-text="replyButtonLabel"
|
||||
:show-audio-recorder="showAudioRecorder"
|
||||
:show-editor-toggle="isAPIInbox && !isOnPrivateNote"
|
||||
:show-emoji-picker="showEmojiPicker"
|
||||
:show-file-upload="showFileUpload"
|
||||
:toggle-audio-recorder-play-pause="toggleAudioRecorderPlayPause"
|
||||
:toggle-audio-recorder="toggleAudioRecorder"
|
||||
:toggle-emoji-picker="toggleEmojiPicker"
|
||||
@selectWhatsappTemplate="openWhatsappTemplateModal"
|
||||
@toggle-editor="toggleRichContentEditor"
|
||||
/>
|
||||
@@ -147,6 +149,7 @@ import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
|
||||
import {
|
||||
MAXIMUM_FILE_UPLOAD_SIZE,
|
||||
MAXIMUM_FILE_UPLOAD_SIZE_TWILIO_SMS_CHANNEL,
|
||||
AUDIO_FORMATS,
|
||||
} from 'shared/constants/messages';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
|
||||
@@ -462,6 +465,12 @@ export default {
|
||||
editorStateId() {
|
||||
return `draft-${this.conversationIdByRoute}-${this.replyType}`;
|
||||
},
|
||||
audioRecordFormat() {
|
||||
if (this.isAWebWidgetInbox) {
|
||||
return AUDIO_FORMATS.WEBM;
|
||||
}
|
||||
return AUDIO_FORMATS.OGG;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
currentChat(conversation) {
|
||||
|
||||
@@ -96,7 +96,9 @@ export default {
|
||||
<style scoped lang="scss">
|
||||
.mention--box {
|
||||
background: var(--white);
|
||||
border-radius: var(--border-radius-normal);
|
||||
border-top: 1px solid var(--color-border);
|
||||
box-shadow: var(--shadow-medium);
|
||||
font-size: var(--font-size-small);
|
||||
left: 0;
|
||||
line-height: 1.2;
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
<template>
|
||||
<div class="message-text--metadata">
|
||||
<span class="time" :class="{ delivered: messageRead }">{{
|
||||
readableTime
|
||||
}}</span>
|
||||
<span
|
||||
class="time"
|
||||
:class="{
|
||||
'has-status-icon':
|
||||
showSentIndicator || showDeliveredIndicator || showReadIndicator,
|
||||
}"
|
||||
>
|
||||
{{ readableTime }}
|
||||
</span>
|
||||
<span v-if="showReadIndicator" class="read-indicator-wrap">
|
||||
<fluent-icon
|
||||
v-tooltip.top-start="$t('CHAT_LIST.MESSAGE_READ')"
|
||||
@@ -11,7 +17,7 @@
|
||||
size="14"
|
||||
/>
|
||||
</span>
|
||||
<span v-if="showDeliveredIndicator" class="read-indicator-wrap">
|
||||
<span v-else-if="showDeliveredIndicator" class="read-indicator-wrap">
|
||||
<fluent-icon
|
||||
v-tooltip.top-start="$t('CHAT_LIST.DELIVERED')"
|
||||
icon="checkmark-double"
|
||||
@@ -19,7 +25,7 @@
|
||||
size="14"
|
||||
/>
|
||||
</span>
|
||||
<span v-if="showSentIndicator" class="read-indicator-wrap">
|
||||
<span v-else-if="showSentIndicator" class="read-indicator-wrap">
|
||||
<fluent-icon
|
||||
v-tooltip.top-start="$t('CHAT_LIST.SENT')"
|
||||
icon="checkmark"
|
||||
@@ -74,17 +80,19 @@
|
||||
import { MESSAGE_TYPE, MESSAGE_STATUS } from 'shared/constants/messages';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||
import { mapGetters } from 'vuex';
|
||||
import timeMixin from '../../../../mixins/time';
|
||||
|
||||
export default {
|
||||
mixins: [inboxMixin],
|
||||
mixins: [inboxMixin, timeMixin],
|
||||
props: {
|
||||
sender: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
readableTime: {
|
||||
type: String,
|
||||
default: '',
|
||||
createdAt: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
storySender: {
|
||||
type: String,
|
||||
@@ -130,12 +138,9 @@ export default {
|
||||
type: [String, Number],
|
||||
default: 0,
|
||||
},
|
||||
messageRead: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({ currentChat: 'getSelectedChat' }),
|
||||
inbox() {
|
||||
return this.$store.getters['inboxes/getInbox'](this.inboxId);
|
||||
},
|
||||
@@ -145,6 +150,9 @@ export default {
|
||||
isOutgoing() {
|
||||
return MESSAGE_TYPE.OUTGOING === this.messageType;
|
||||
},
|
||||
isTemplate() {
|
||||
return MESSAGE_TYPE.TEMPLATE === this.messageType;
|
||||
},
|
||||
isDelivered() {
|
||||
return MESSAGE_STATUS.DELIVERED === this.messageStatus;
|
||||
},
|
||||
@@ -154,6 +162,9 @@ export default {
|
||||
isSent() {
|
||||
return MESSAGE_STATUS.SENT === this.messageStatus;
|
||||
},
|
||||
readableTime() {
|
||||
return this.messageStamp(this.createdAt, 'LLL d, h:mm a');
|
||||
},
|
||||
screenName() {
|
||||
const { additional_attributes: additionalAttributes = {} } =
|
||||
this.sender || {};
|
||||
@@ -174,28 +185,52 @@ export default {
|
||||
const { storySender, storyId } = this;
|
||||
return `https://www.instagram.com/stories/${storySender}/${storyId}`;
|
||||
},
|
||||
showStatusIndicators() {
|
||||
if ((this.isOutgoing || this.isTemplate) && !this.isPrivate) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
showSentIndicator() {
|
||||
return (
|
||||
this.isOutgoing &&
|
||||
this.sourceId &&
|
||||
(this.isAnEmailChannel || (this.isAWhatsAppChannel && this.isSent))
|
||||
);
|
||||
if (!this.showStatusIndicators) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.isAnEmailChannel) {
|
||||
return !!this.sourceId;
|
||||
}
|
||||
|
||||
if (this.isAWhatsAppChannel) {
|
||||
return this.sourceId && this.isSent;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
showDeliveredIndicator() {
|
||||
return (
|
||||
this.isOutgoing &&
|
||||
this.sourceId &&
|
||||
this.isAWhatsAppChannel &&
|
||||
this.isDelivered
|
||||
);
|
||||
if (!this.showStatusIndicators) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.isAWhatsAppChannel) {
|
||||
return this.sourceId && this.isDelivered;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
showReadIndicator() {
|
||||
return (
|
||||
this.isOutgoing &&
|
||||
this.sourceId &&
|
||||
this.isAWhatsAppChannel &&
|
||||
this.isRead
|
||||
);
|
||||
if (!this.showStatusIndicators) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.isAWebWidgetInbox) {
|
||||
const { contact_last_seen_at: contactLastSeenAt } = this.currentChat;
|
||||
return contactLastSeenAt >= this.createdAt;
|
||||
}
|
||||
|
||||
if (this.isAWhatsAppChannel) {
|
||||
return this.sourceId && this.isRead;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
@@ -218,12 +253,13 @@ export default {
|
||||
|
||||
.action--icon {
|
||||
color: var(--white);
|
||||
|
||||
&.read-tick {
|
||||
color: var(--v-100);
|
||||
}
|
||||
|
||||
&.read-indicator {
|
||||
color: var(--g-300);
|
||||
color: var(--g-200);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -288,8 +324,9 @@ export default {
|
||||
position: absolute;
|
||||
right: var(--space-small);
|
||||
white-space: nowrap;
|
||||
&.delivered {
|
||||
right: var(--space-medium);
|
||||
|
||||
&.has-status-icon {
|
||||
right: var(--space-large);
|
||||
line-height: 2;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<dyte-video-call
|
||||
v-if="showDyteIntegration"
|
||||
:message-id="messageId"
|
||||
:meeting-data="contentAttributes.data"
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
import DyteVideoCall from './integrations/Dyte.vue';
|
||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||
|
||||
export default {
|
||||
components: { DyteVideoCall },
|
||||
mixins: [inboxMixin],
|
||||
props: {
|
||||
messageId: {
|
||||
type: [String, Number],
|
||||
default: 0,
|
||||
},
|
||||
contentAttributes: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
inboxId: {
|
||||
type: [String, Number],
|
||||
default: 0,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
showDyteIntegration() {
|
||||
const isEnabledOnTheInbox = this.isAPIInbox || this.isAWebWidgetInbox;
|
||||
return isEnabledOnTheInbox && this.contentAttributes.type === 'dyte';
|
||||
},
|
||||
inbox() {
|
||||
return this.$store.getters['inboxes/getInbox'](this.inboxId);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -35,10 +35,6 @@ export default {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
readableTime: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
isEmail: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
|
||||
+103
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div>
|
||||
<woot-button
|
||||
size="small"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
icon="video-add"
|
||||
class="join-call-button"
|
||||
:is-loading="isLoading"
|
||||
@click="joinTheCall"
|
||||
>
|
||||
{{ $t('INTEGRATION_SETTINGS.DYTE.CLICK_HERE_TO_JOIN') }}
|
||||
</woot-button>
|
||||
<div v-if="dyteAuthToken" class="video-call--container">
|
||||
<iframe
|
||||
:src="meetingLink"
|
||||
allow="camera;microphone;fullscreen;display-capture;picture-in-picture;clipboard-write;"
|
||||
/>
|
||||
<woot-button
|
||||
size="small"
|
||||
variant="smooth"
|
||||
color-scheme="secondary"
|
||||
class="join-call-button"
|
||||
@click="leaveTheRoom"
|
||||
>
|
||||
{{ $t('INTEGRATION_SETTINGS.DYTE.LEAVE_THE_ROOM') }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import DyteAPI from 'dashboard/api/integrations/dyte';
|
||||
import { buildDyteURL } from 'shared/helpers/IntegrationHelper';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
|
||||
export default {
|
||||
mixins: [alertMixin],
|
||||
props: {
|
||||
messageId: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
meetingData: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return { isLoading: false, dyteAuthToken: '', isSDKMounted: false };
|
||||
},
|
||||
computed: {
|
||||
meetingLink() {
|
||||
return buildDyteURL(this.meetingData.room_name, this.dyteAuthToken);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async joinTheCall() {
|
||||
this.isLoading = true;
|
||||
try {
|
||||
const {
|
||||
data: { authResponse: { authToken } = {} } = {},
|
||||
} = await DyteAPI.addParticipantToMeeting(this.messageId);
|
||||
this.dyteAuthToken = authToken;
|
||||
} catch (err) {
|
||||
this.showAlert(this.$t('INTEGRATION_SETTINGS.DYTE.JOIN_ERROR'));
|
||||
} finally {
|
||||
this.isLoading = false;
|
||||
}
|
||||
},
|
||||
leaveTheRoom() {
|
||||
this.dyteAuthToken = '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
.join-call-button {
|
||||
margin: var(--space-small) 0;
|
||||
}
|
||||
|
||||
.video-call--container {
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: var(--z-index-high);
|
||||
padding: var(--space-smaller);
|
||||
background: var(--b-800);
|
||||
|
||||
iframe {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
button {
|
||||
position: absolute;
|
||||
top: var(--space-smaller);
|
||||
right: 16rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -70,7 +70,9 @@ export default {
|
||||
.mention--box {
|
||||
background: var(--white);
|
||||
border-bottom: var(--space-small) solid var(--white);
|
||||
border-radius: var(--border-radius-normal);
|
||||
border-top: 1px solid var(--color-border);
|
||||
box-shadow: var(--shadow-medium);
|
||||
left: 0;
|
||||
max-height: 14rem;
|
||||
overflow: auto;
|
||||
|
||||
@@ -1,23 +1,14 @@
|
||||
<template>
|
||||
<modal :show.sync="show" :on-close="cancel">
|
||||
<div class="column content-box">
|
||||
<woot-modal-header :header-title="title" />
|
||||
<div class="row modal-content">
|
||||
<div class="medium-12 columns">
|
||||
<p>
|
||||
{{ description }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<div class="medium-12 columns">
|
||||
<woot-button @click="confirm">
|
||||
{{ confirmLabel }}
|
||||
</woot-button>
|
||||
<button class="button clear" @click="cancel">
|
||||
{{ cancelLabel }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<woot-modal-header :header-title="title" :header-content="description" />
|
||||
<div class="modal-footer">
|
||||
<woot-button variant="clear" @click="cancel">
|
||||
{{ cancelLabel }}
|
||||
</woot-button>
|
||||
<woot-button @click="confirm">
|
||||
{{ confirmLabel }}
|
||||
</woot-button>
|
||||
</div>
|
||||
</div>
|
||||
</modal>
|
||||
@@ -73,3 +64,8 @@ export default {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.modal-container .modal-footer {
|
||||
padding: var(--space-normal) var(--space-medium);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,89 +1,87 @@
|
||||
<template>
|
||||
<transition name="slide-up">
|
||||
<div class="modal-mask">
|
||||
<div v-on-clickaway="() => $emit('clickaway')" class="modal-container">
|
||||
<div class="header-wrap">
|
||||
<div class="title-shortcut-key__wrap">
|
||||
<h2 class="page-title">
|
||||
{{ $t('SIDEBAR_ITEMS.KEYBOARD_SHORTCUTS') }}
|
||||
</h2>
|
||||
<div class="shortcut-key__wrap">
|
||||
<p class="shortcut-key">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.WINDOWS_KEY_AND_COMMAND_KEY') }}
|
||||
</p>
|
||||
<p class="shortcut-key key">
|
||||
<woot-modal :show="show" size="medium" :on-close="() => $emit('close')">
|
||||
<div class="column content-box">
|
||||
<woot-modal-header
|
||||
:header-title="$t('SIDEBAR_ITEMS.KEYBOARD_SHORTCUTS')"
|
||||
/>
|
||||
<div class="shortcut__wrap margin-top-3">
|
||||
<div class="title-key__wrap">
|
||||
<h5 class="text-block-title">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.TOGGLE_MODAL') }}
|
||||
</h5>
|
||||
<div class="shortcut-key__wrap">
|
||||
<p class="shortcut-key">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.WINDOWS_KEY_AND_COMMAND_KEY') }}
|
||||
</p>
|
||||
<p class="shortcut-key key">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.FORWARD_SLASH_KEY') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="shortcut__wrap">
|
||||
<div class="title-key__wrap">
|
||||
<h5 class="text-block-title">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.TITLE.OPEN_CONVERSATION') }}
|
||||
</h5>
|
||||
<div class="shortcut-key__wrap">
|
||||
<div class="open-conversation__key">
|
||||
<span class="shortcut-key">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.ALT_OR_OPTION_KEY') }}
|
||||
</span>
|
||||
<span class="shortcut-key">
|
||||
J
|
||||
</span>
|
||||
<span class="forward-slash text-block-title">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.FORWARD_SLASH_KEY') }}
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<span class="shortcut-key">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.ALT_OR_OPTION_KEY') }}
|
||||
</span>
|
||||
<span class="shortcut-key key">
|
||||
K
|
||||
</span>
|
||||
</div>
|
||||
<button class="cursor-pointer" @click="$emit('close')">
|
||||
<fluent-icon icon="dismiss" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="shortcut__wrap">
|
||||
<div class="title-key__wrap">
|
||||
<span class="sub-block-title">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.TITLE.OPEN_CONVERSATION') }}
|
||||
<div class="title-key__wrap">
|
||||
<h5 class="text-block-title">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.TITLE.RESOLVE_AND_NEXT') }}
|
||||
</h5>
|
||||
<div class="shortcut-key__wrap">
|
||||
<span class="shortcut-key">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.WINDOWS_KEY_AND_COMMAND_KEY') }}
|
||||
</span>
|
||||
<span class="shortcut-key">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.ALT_OR_OPTION_KEY') }}
|
||||
</span>
|
||||
<span class="shortcut-key key">
|
||||
E
|
||||
</span>
|
||||
<div class="shortcut-key__wrap">
|
||||
<div class="open-conversation__key">
|
||||
<span class="shortcut-key">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.ALT_OR_OPTION_KEY') }}
|
||||
</span>
|
||||
<span class="shortcut-key">
|
||||
J
|
||||
</span>
|
||||
<span class="forward-slash sub-block-title">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.FORWARD_SLASH_KEY') }}
|
||||
</span>
|
||||
</div>
|
||||
<span class="shortcut-key">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.ALT_OR_OPTION_KEY') }}
|
||||
</span>
|
||||
<span class="shortcut-key key">
|
||||
K
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="title-key__wrap">
|
||||
<span class="sub-block-title">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.TITLE.RESOLVE_AND_NEXT') }}
|
||||
</div>
|
||||
<div
|
||||
v-for="shortcutKey in shortcutKeys"
|
||||
:key="shortcutKey.id"
|
||||
class="title-key__wrap"
|
||||
>
|
||||
<h5 class="text-block-title">
|
||||
{{ title(shortcutKey) }}
|
||||
</h5>
|
||||
<div class="shortcut-key__wrap">
|
||||
<span class="shortcut-key">
|
||||
{{ shortcutKey.firstkey }}
|
||||
</span>
|
||||
<div class="shortcut-key__wrap">
|
||||
<span class="shortcut-key">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.WINDOWS_KEY_AND_COMMAND_KEY') }}
|
||||
</span>
|
||||
<span class="shortcut-key">
|
||||
{{ $t('KEYBOARD_SHORTCUTS.KEYS.ALT_OR_OPTION_KEY') }}
|
||||
</span>
|
||||
<span class="shortcut-key key">
|
||||
E
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
v-for="shortcutKey in shortcutKeys"
|
||||
:key="shortcutKey.id"
|
||||
class="title-key__wrap"
|
||||
>
|
||||
<span class="sub-block-title">
|
||||
{{ title(shortcutKey) }}
|
||||
<span class="shortcut-key key">
|
||||
{{ shortcutKey.secondKey }}
|
||||
</span>
|
||||
<div class="shortcut-key__wrap">
|
||||
<span class="shortcut-key">
|
||||
{{ shortcutKey.firstkey }}
|
||||
</span>
|
||||
<span class="shortcut-key key">
|
||||
{{ shortcutKey.secondKey }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
</woot-modal>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
@@ -92,6 +90,12 @@ import { SHORTCUT_KEYS } from './constants';
|
||||
|
||||
export default {
|
||||
mixins: [clickaway],
|
||||
props: {
|
||||
show: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
shortcutKeys: SHORTCUT_KEYS,
|
||||
@@ -106,19 +110,6 @@ export default {
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.modal-container {
|
||||
padding: var(--space-medium) var(--space-large) var(--space-large)
|
||||
var(--space-large);
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.header-wrap {
|
||||
align-items: flex-start;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: var(--space-slab);
|
||||
}
|
||||
|
||||
.title-shortcut-key__wrap {
|
||||
display: flex;
|
||||
}
|
||||
@@ -140,6 +131,7 @@ export default {
|
||||
grid-template-columns: repeat(2, 0.5fr);
|
||||
gap: var(--space-smaller) var(--space-large);
|
||||
margin-top: var(--space-small);
|
||||
padding: 0 var(--space-large) var(--space-large);
|
||||
}
|
||||
|
||||
.title-key__wrap {
|
||||
@@ -149,11 +141,6 @@ export default {
|
||||
min-width: 40rem;
|
||||
}
|
||||
|
||||
.sub-block-title {
|
||||
font-size: var(--font-size-small);
|
||||
font-weight: var(--font-weight-medium);
|
||||
}
|
||||
|
||||
.forward-slash {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -23,5 +23,6 @@ export default {
|
||||
},
|
||||
DOCS_URL: '//www.chatwoot.com/docs/product/',
|
||||
TESTIMONIAL_URL: 'https://testimonials.cdn.chatwoot.com/content.json',
|
||||
SMALL_SCREEN_BREAKPOINT: 1024,
|
||||
};
|
||||
export const DEFAULT_REDIRECT_URL = '/app/';
|
||||
|
||||
@@ -0,0 +1,148 @@
|
||||
import { MESSAGE_TYPE } from 'shared/constants/messages';
|
||||
import { showBadgeOnFavicon } from './faviconHelper';
|
||||
import { initFaviconSwitcher } from './faviconHelper';
|
||||
import {
|
||||
getAlertAudio,
|
||||
initOnEvents,
|
||||
} from 'shared/helpers/AudioNotificationHelper';
|
||||
|
||||
const NOTIFICATION_TIME = 30000;
|
||||
|
||||
class DashboardAudioNotificationHelper {
|
||||
constructor() {
|
||||
this.recurringNotificationTimer = null;
|
||||
this.audioAlertType = 'none';
|
||||
this.playAlertOnlyWhenHidden = true;
|
||||
this.alertIfUnreadConversationExist = false;
|
||||
this.currentUserId = null;
|
||||
this.audioAlertTone = 'ding';
|
||||
}
|
||||
|
||||
setInstanceValues = ({
|
||||
currentUserId,
|
||||
alwaysPlayAudioAlert,
|
||||
alertIfUnreadConversationExist,
|
||||
audioAlertType,
|
||||
audioAlertTone,
|
||||
}) => {
|
||||
this.audioAlertType = audioAlertType;
|
||||
this.playAlertOnlyWhenHidden = !alwaysPlayAudioAlert;
|
||||
this.alertIfUnreadConversationExist = alertIfUnreadConversationExist;
|
||||
this.currentUserId = currentUserId;
|
||||
this.audioAlertTone = audioAlertTone;
|
||||
initOnEvents.forEach(e => {
|
||||
document.addEventListener(e, this.onAudioListenEvent, false);
|
||||
});
|
||||
initFaviconSwitcher();
|
||||
};
|
||||
|
||||
onAudioListenEvent = async () => {
|
||||
try {
|
||||
await getAlertAudio('', {
|
||||
type: 'dashboard',
|
||||
alertTone: this.audioAlertTone,
|
||||
});
|
||||
initOnEvents.forEach(event => {
|
||||
document.removeEventListener(event, this.onAudioListenEvent, false);
|
||||
});
|
||||
this.playAudioEvery30Seconds();
|
||||
} catch (error) {
|
||||
// Ignore audio fetch errors
|
||||
}
|
||||
};
|
||||
|
||||
executeRecurringNotification = () => {
|
||||
const mineConversation = window.WOOT.$store.getters.getMineChats({
|
||||
assigneeType: 'me',
|
||||
status: 'open',
|
||||
});
|
||||
const hasUnreadConversation = mineConversation.some(conv => {
|
||||
return conv.unread_count > 0;
|
||||
});
|
||||
|
||||
const shouldPlayAlert = !this.playAlertOnlyWhenHidden || document.hidden;
|
||||
|
||||
if (hasUnreadConversation && shouldPlayAlert) {
|
||||
window.playAudioAlert();
|
||||
showBadgeOnFavicon();
|
||||
}
|
||||
this.clearSetTimeout();
|
||||
};
|
||||
|
||||
clearSetTimeout = () => {
|
||||
if (this.recurringNotificationTimer) {
|
||||
clearTimeout(this.recurringNotificationTimer);
|
||||
}
|
||||
this.recurringNotificationTimer = setTimeout(
|
||||
this.executeRecurringNotification,
|
||||
NOTIFICATION_TIME
|
||||
);
|
||||
};
|
||||
|
||||
playAudioEvery30Seconds = () => {
|
||||
// Audio alert is disabled dismiss the timer
|
||||
if (this.audioAlertType === 'none') {
|
||||
return;
|
||||
}
|
||||
// If assigned conversation flag is disabled dismiss the timer
|
||||
if (!this.alertIfUnreadConversationExist) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.clearSetTimeout();
|
||||
};
|
||||
|
||||
isConversationAssignedToCurrentUser = message => {
|
||||
const conversationAssigneeId = message?.conversation?.assignee_id;
|
||||
return conversationAssigneeId === this.currentUserId;
|
||||
};
|
||||
|
||||
isMessageFromCurrentConversation = message => {
|
||||
return (
|
||||
window.WOOT.$store.getters.getSelectedChat?.id === message.conversation_id
|
||||
);
|
||||
};
|
||||
|
||||
isMessageFromCurrentUser = message => {
|
||||
return message?.sender_id === this.currentUserId;
|
||||
};
|
||||
|
||||
shouldNotifyOnMessage = message => {
|
||||
if (this.audioAlertType === 'mine') {
|
||||
return this.isConversationAssignedToCurrentUser(message);
|
||||
}
|
||||
return this.audioAlertType === 'all';
|
||||
};
|
||||
|
||||
onNewMessage = message => {
|
||||
// If the message is sent by the current user or the
|
||||
// correct notification is not enabled, then dismiss the alert
|
||||
if (
|
||||
this.isMessageFromCurrentUser(message) ||
|
||||
!this.shouldNotifyOnMessage(message)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the message type is not incoming or private, then dismiss the alert
|
||||
const { message_type: messageType, private: isPrivate } = message;
|
||||
if (messageType !== MESSAGE_TYPE.INCOMING && !isPrivate) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the user looking at the conversation, then dismiss the alert
|
||||
if (this.isMessageFromCurrentConversation(message) && !document.hidden) {
|
||||
return;
|
||||
}
|
||||
// If the user has disabled alerts when active on the dashboard, the dismiss the alert
|
||||
if (this.playAlertOnlyWhenHidden && !document.hidden) {
|
||||
return;
|
||||
}
|
||||
|
||||
window.playAudioAlert();
|
||||
showBadgeOnFavicon();
|
||||
this.playAudioEvery30Seconds();
|
||||
};
|
||||
}
|
||||
|
||||
export default new DashboardAudioNotificationHelper();
|
||||
@@ -1,6 +1,6 @@
|
||||
import AuthAPI from '../api/auth';
|
||||
import BaseActionCableConnector from '../../shared/helpers/BaseActionCableConnector';
|
||||
import { newMessageNotification } from 'shared/helpers/AudioNotificationHelper';
|
||||
import DashboardAudioNotificationHelper from './AudioAlerts/DashboardAudioNotificationHelper';
|
||||
|
||||
class ActionCableConnector extends BaseActionCableConnector {
|
||||
constructor(app, pubsubToken) {
|
||||
@@ -74,7 +74,7 @@ class ActionCableConnector extends BaseActionCableConnector {
|
||||
onLogout = () => AuthAPI.logout();
|
||||
|
||||
onMessageCreated = data => {
|
||||
newMessageNotification(data);
|
||||
DashboardAudioNotificationHelper.onNewMessage(data);
|
||||
this.app.$store.dispatch('addMessage', data);
|
||||
};
|
||||
|
||||
|
||||
@@ -97,8 +97,9 @@ export const generateConditionOptions = (options, key = 'id') => {
|
||||
});
|
||||
};
|
||||
|
||||
export const getActionOptions = ({ teams, labels, type }) => {
|
||||
export const getActionOptions = ({ agents, teams, labels, type }) => {
|
||||
const actionsMap = {
|
||||
assign_agent: agents,
|
||||
assign_team: teams,
|
||||
send_email_to_team: teams,
|
||||
add_label: generateConditionOptions(labels, 'title'),
|
||||
@@ -178,7 +179,7 @@ export const getDefaultConditions = eventName => {
|
||||
export const getDefaultActions = () => {
|
||||
return [
|
||||
{
|
||||
action_name: 'assign_team',
|
||||
action_name: 'assign_agent',
|
||||
action_params: [],
|
||||
},
|
||||
];
|
||||
@@ -213,7 +214,7 @@ export const isCustomAttribute = (attrs, key) => {
|
||||
|
||||
export const generateCustomAttributes = (
|
||||
conversationAttributes = [],
|
||||
contactAttribtues = [],
|
||||
contactAttributes = [],
|
||||
conversationlabel,
|
||||
contactlabel
|
||||
) => {
|
||||
@@ -228,14 +229,14 @@ export const generateCustomAttributes = (
|
||||
...conversationAttributes
|
||||
);
|
||||
}
|
||||
if (contactAttribtues.length) {
|
||||
if (contactAttributes.length) {
|
||||
customAttributes.push(
|
||||
{
|
||||
key: `contact_custom_attribute`,
|
||||
name: contactlabel,
|
||||
disabled: true,
|
||||
},
|
||||
...contactAttribtues
|
||||
...contactAttributes
|
||||
);
|
||||
}
|
||||
return customAttributes;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import AnalyticsHelper from './AnalyticsHelper';
|
||||
import DashboardAudioNotificationHelper from './AudioAlerts/DashboardAudioNotificationHelper';
|
||||
|
||||
export const CHATWOOT_SET_USER = 'CHATWOOT_SET_USER';
|
||||
export const CHATWOOT_RESET = 'CHATWOOT_RESET';
|
||||
@@ -13,6 +14,25 @@ export const initializeAnalyticsEvents = () => {
|
||||
window.bus.$on(ANALYTICS_RESET, () => {});
|
||||
};
|
||||
|
||||
const initializeAudioAlerts = user => {
|
||||
// InitializeAudioNotifications
|
||||
const { ui_settings: uiSettings } = user || {};
|
||||
const {
|
||||
always_play_audio_alert: alwaysPlayAudioAlert,
|
||||
enable_audio_alerts: audioAlertType,
|
||||
alert_if_unread_assigned_conversation_exist: alertIfUnreadConversationExist,
|
||||
notification_tone: audioAlertTone,
|
||||
} = uiSettings;
|
||||
|
||||
DashboardAudioNotificationHelper.setInstanceValues({
|
||||
currentUserId: user.id,
|
||||
audioAlertType: audioAlertType || 'none',
|
||||
audioAlertTone: audioAlertTone || 'ding',
|
||||
alwaysPlayAudioAlert: alwaysPlayAudioAlert || false,
|
||||
alertIfUnreadConversationExist: alertIfUnreadConversationExist || false,
|
||||
});
|
||||
};
|
||||
|
||||
export const initializeChatwootEvents = () => {
|
||||
window.bus.$on(CHATWOOT_RESET, () => {
|
||||
if (window.$chatwoot) {
|
||||
@@ -32,5 +52,7 @@ export const initializeChatwootEvents = () => {
|
||||
cloudCustomer: 'true',
|
||||
});
|
||||
}
|
||||
|
||||
initializeAudioAlerts(user);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -24,8 +24,11 @@
|
||||
"TITLE": "Select an agent bot",
|
||||
"DESC": "You can set an agent bot from the list to this inbox. The bot can initially handle the conversation and transfer it to an agent when needed.",
|
||||
"SUBMIT": "تحديث",
|
||||
"DISCONNECT": "Disconnect Bot",
|
||||
"SUCCESS_MESSAGE": "Successfully updated the agent bot",
|
||||
"DISCONNECTED_SUCCESS_MESSAGE": "Successfully disconnected the agent bot",
|
||||
"ERROR_MESSAGE": "Could not update the agent bot, please try again later",
|
||||
"DISCONNECTED_ERROR_MESSAGE": "Could not disconnect the agent bot, please try again later",
|
||||
"SELECT_PLACEHOLDER": "Select Bot"
|
||||
},
|
||||
"ADD": {
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
}
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "تم إضافة سمة مخصصة بنجاح",
|
||||
"ERROR_MESSAGE": "تعذر إنشاء سمة مخصصة، الرجاء المحاولة مرة أخرى لاحقاً"
|
||||
"SUCCESS_MESSAGE": "تم إضافة سمة مخصصة بنجاح!",
|
||||
"ERROR_MESSAGE": "تعذر إنشاء سمة مخصصة، الرجاء المحاولة مرة أخرى لاحقاً."
|
||||
}
|
||||
},
|
||||
"DELETE": {
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"BULK_ACTION": {
|
||||
"CONVERSATIONS_SELECTED": "%{conversationCount} المحادثات المحددة",
|
||||
"AGENT_SELECT_LABEL": "اختر وكيل",
|
||||
"ASSIGN_CONFIRMATION_LABEL": "Are you sure to assign %{conversationCount} %{conversationLabel} to",
|
||||
"UNASSIGN_CONFIRMATION_LABEL": "Are you sure to unassign %{conversationCount} %{conversationLabel}?",
|
||||
"ASSIGN_CONFIRMATION_LABEL": "هل أنت متأكد من أنك تريد تعيين %{conversationCount} %{conversationLabel} إلى",
|
||||
"UNASSIGN_CONFIRMATION_LABEL": "هل أنت متأكد من إلغاء تعيين %{conversationCount} %{conversationLabel}؟",
|
||||
"GO_BACK_LABEL": "العودة للخلف",
|
||||
"ASSIGN_LABEL": "تكليف",
|
||||
"YES": "نعم",
|
||||
@@ -31,10 +31,10 @@
|
||||
"TEAMS": {
|
||||
"TEAM_SELECT_LABEL": "اختيار فريق",
|
||||
"NONE": "لا شيء",
|
||||
"NO_TEAMS_AVAILABLE": "There are no teams added to this account yet.",
|
||||
"ASSIGN_SELECTED_TEAMS": "Assign selected team",
|
||||
"ASSIGN_SUCCESFUL": "Teams assiged successfully",
|
||||
"ASSIGN_FAILED": "Failed to assign team, please try again"
|
||||
"NO_TEAMS_AVAILABLE": "لا توجد فرق مضافة إلى هذا الحساب حتى الآن.",
|
||||
"ASSIGN_SELECTED_TEAMS": "تعيين فريق محدد",
|
||||
"ASSIGN_SUCCESFUL": "تم تعيين الفرق بنجاح",
|
||||
"ASSIGN_FAILED": "فشل تعيين الفريق، الرجاء المحاولة مرة أخرى"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,8 +57,8 @@
|
||||
"REPLY_TO_TWEET": "الرد على هذه التغريدة",
|
||||
"LINK_TO_STORY": "الذهاب إلى قصة الإنستقرام",
|
||||
"SENT": "Sent successfully",
|
||||
"READ": "Read successfully",
|
||||
"DELIVERED": "Delivered successfully",
|
||||
"READ": "تمت القراءة بنجاح",
|
||||
"DELIVERED": "تم الإرسال بنجاح",
|
||||
"NO_MESSAGES": "لا توجد رسائل",
|
||||
"NO_CONTENT": "لم يتم العثور على محتوى",
|
||||
"HIDE_QUOTED_TEXT": "Hide Quoted Text",
|
||||
|
||||
@@ -214,7 +214,7 @@
|
||||
"CONVERSATION_INFO": "معلومات المحادثة",
|
||||
"CONTACT_ATTRIBUTES": "سمات جهة الاتصال",
|
||||
"PREVIOUS_CONVERSATION": "المحادثات السابقة",
|
||||
"MACROS": "Macros"
|
||||
"MACROS": "ماكروس"
|
||||
}
|
||||
},
|
||||
"CONVERSATION_CUSTOM_ATTRIBUTES": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"EMOJI": {
|
||||
"PLACEHOLDER": "Search emojis",
|
||||
"NOT_FOUND": "No emoji match your search"
|
||||
"PLACEHOLDER": "ابحث في الايموجي",
|
||||
"NOT_FOUND": "لا يوجد إيموجي يطابق بحثك"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"ERROR": "الرجاء إدخال اسم حساب صحيح"
|
||||
},
|
||||
"LANGUAGE": {
|
||||
"LABEL": "Site language",
|
||||
"LABEL": "لغة الموقع",
|
||||
"PLACEHOLDER": "اسم الحساب الخاص بك",
|
||||
"ERROR": ""
|
||||
},
|
||||
@@ -55,7 +55,7 @@
|
||||
"ENTER_TO_SELECT": "اضغط على زر الإدخال للاختيار",
|
||||
"ENTER_TO_REMOVE": "اضغط على زر الإدخال للحذف",
|
||||
"SELECT_ONE": "اختر واحدا",
|
||||
"SELECT": "Select"
|
||||
"SELECT": "اختر"
|
||||
}
|
||||
},
|
||||
"NOTIFICATIONS_PAGE": {
|
||||
@@ -139,6 +139,6 @@
|
||||
}
|
||||
},
|
||||
"DASHBOARD_APPS": {
|
||||
"LOADING_MESSAGE": "Loading Dashboard App..."
|
||||
"LOADING_MESSAGE": "تحميل تطبيق لوحة التحكم..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,6 +53,10 @@
|
||||
"ENABLE": "إنشاء محادثات من التغريدات المشار إليها"
|
||||
}
|
||||
},
|
||||
"MICROSOFT": {
|
||||
"HELP": "To add your Microsoft account as a channel, you need to authenticate your Microsoft account by clicking on 'Sign in with Microsoft' ",
|
||||
"ERROR_MESSAGE": "There was an error connecting to Microsoft, please try again"
|
||||
},
|
||||
"WEBSITE_CHANNEL": {
|
||||
"TITLE": "قناة الموقع",
|
||||
"DESC": "قم بإنشاء قناة تواصل لموقع الويب الخاص بك وابدأ في استقبال الرسائل من عملائك عبر صندوق الدردشة المباشرة.",
|
||||
@@ -134,7 +138,7 @@
|
||||
"PHONE_NUMBER": {
|
||||
"LABEL": "رقم الهاتف",
|
||||
"PLACEHOLDER": "الرجاء إدخال رقم الهاتف الذي سيتم إرسال الرسائل منه.",
|
||||
"ERROR": "Please provide a valid phone number that starts with a `+` sign and does not contain any spaces."
|
||||
"ERROR": "الرجاء تقديم رقم هاتف صالح يبدأ بإشارة '+' ولا يحتوي على أي مسافات."
|
||||
},
|
||||
"API_CALLBACK": {
|
||||
"TITLE": "عنوان Callback URL",
|
||||
@@ -185,7 +189,7 @@
|
||||
"PHONE_NUMBER": {
|
||||
"LABEL": "رقم الهاتف",
|
||||
"PLACEHOLDER": "الرجاء إدخال رقم الهاتف الذي سيتم إرسال الرسائل منه.",
|
||||
"ERROR": "Please provide a valid phone number that starts with a `+` sign and does not contain any spaces."
|
||||
"ERROR": "الرجاء تقديم رقم هاتف صالح يبدأ بإشارة '+' ولا يحتوي على أي مسافات."
|
||||
},
|
||||
"SUBMIT_BUTTON": "إنشاء قناة عرض التردد",
|
||||
"API": {
|
||||
@@ -214,7 +218,7 @@
|
||||
"PHONE_NUMBER": {
|
||||
"LABEL": "رقم الهاتف",
|
||||
"PLACEHOLDER": "الرجاء إدخال رقم الهاتف الذي سيتم إرسال الرسائل منه.",
|
||||
"ERROR": "Please provide a valid phone number that starts with a `+` sign and does not contain any spaces."
|
||||
"ERROR": "الرجاء تقديم رقم هاتف صالح يبدأ بإشارة '+' ولا يحتوي على أي مسافات."
|
||||
},
|
||||
"PHONE_NUMBER_ID": {
|
||||
"LABEL": "رقم الهاتف",
|
||||
@@ -239,9 +243,9 @@
|
||||
},
|
||||
"API_CALLBACK": {
|
||||
"TITLE": "عنوان Callback URL",
|
||||
"SUBTITLE": "You have to configure the webhook URL and the verification token in the Facebook Developer portal with the values shown below.",
|
||||
"SUBTITLE": "يجب عليك تكوين رابط الويب هوك و رمز التحقق في بوابة مطور الفيسبوك مع القيم الموضحة أدناه.",
|
||||
"WEBHOOK_URL": "رابط Webhook",
|
||||
"WEBHOOK_VERIFICATION_TOKEN": "Webhook Verification Token"
|
||||
"WEBHOOK_VERIFICATION_TOKEN": "رمز التحقق من Webhook"
|
||||
},
|
||||
"SUBMIT_BUTTON": "إنشاء قناة واتساب",
|
||||
"API": {
|
||||
@@ -421,7 +425,7 @@
|
||||
"PRE_CHAT_FORM": "نموذج ما قبل الدردشة",
|
||||
"BUSINESS_HOURS": "ساعات العمل",
|
||||
"WIDGET_BUILDER": "منشئ اللايف شات",
|
||||
"BOT_CONFIGURATION": "Bot Configuration"
|
||||
"BOT_CONFIGURATION": "اعدادات البوت"
|
||||
},
|
||||
"SETTINGS": "الإعدادات",
|
||||
"FEATURES": {
|
||||
@@ -445,8 +449,8 @@
|
||||
"ENABLE_CSAT_SUB_TEXT": "تمكين/تعطيل تقييم خدمة العملاء بعد إنتهاء المحادثة",
|
||||
"ENABLE_CONTINUITY_VIA_EMAIL": "تمكين استمرارية المحادثة عبر البريد الإلكتروني",
|
||||
"ENABLE_CONTINUITY_VIA_EMAIL_SUB_TEXT": "المحادثات ستستمر عبر البريد الإلكتروني إذا كان عنوان البريد الإلكتروني لجهة الاتصال متاحاً.",
|
||||
"LOCK_TO_SINGLE_CONVERSATION": "Lock to single conversation",
|
||||
"LOCK_TO_SINGLE_CONVERSATION_SUB_TEXT": "Enable or disable multiple conversations for the same contact in this inbox",
|
||||
"LOCK_TO_SINGLE_CONVERSATION": "قفل إلى محادثة واحدة",
|
||||
"LOCK_TO_SINGLE_CONVERSATION_SUB_TEXT": "تمكين أو تعطيل محادثات متعددة لنفس جهة الاتصال في هذا البريد الوارد",
|
||||
"INBOX_UPDATE_TITLE": "إعدادات قناة التواصل",
|
||||
"INBOX_UPDATE_SUB_TEXT": "تحديث إعدادات قناة التواصل",
|
||||
"AUTO_ASSIGNMENT_SUB_TEXT": "تمكين أو تعطيل الإسناد التلقائي للمحادثات الجديدة إلى الموظفين المضافين إلى قناة التواصل هذه.",
|
||||
@@ -548,6 +552,10 @@
|
||||
},
|
||||
"ENABLE_SSL": "تمكين SSL"
|
||||
},
|
||||
"MICROSOFT": {
|
||||
"TITLE": "Microsoft",
|
||||
"SUBTITLE": "Reauthorize your MICROSOFT account"
|
||||
},
|
||||
"SMTP": {
|
||||
"TITLE": "SMTP",
|
||||
"SUBTITLE": "تعيين تفاصيل IMAP الخاصة بك",
|
||||
|
||||
@@ -76,6 +76,13 @@
|
||||
"BODY": "<br/><p>ستتم الآن مزامنة جميع المحادثات الواردة إلى <b><i>محادثة الزبائن</i></b> داخل مكان العمل الخاص بك.</p><p>الرد على موضوع محادثة في <b><i>محادثة العملاء</i></b> قناة الركود سوف تقوم بإنشاء رد على العميل من خلال المحادثة.</p><p>ابدأ الردود ب <b><i>ملاحظة:</i></b> لإنشاء ملاحظات خاصة بدلاً من الردود.</p><p>إذا كان للرد على slack ملف تعريف الوكيل في الدردشة تحت نفس البريد الإلكتروني، فسيتم ربط الردود وفقا لذلك.</p><p>عندما لا يكون للرد ملف شخصي للوكيل المرتبط، ستتم الردود من ملف بوت الشخصي.</p>"
|
||||
}
|
||||
},
|
||||
"DYTE": {
|
||||
"CLICK_HERE_TO_JOIN": "Click here to join",
|
||||
"LEAVE_THE_ROOM": "Leave the room",
|
||||
"START_VIDEO_CALL_HELP_TEXT": "Start a new video call with the customer",
|
||||
"JOIN_ERROR": "There was an error joining the call, please try again",
|
||||
"CREATE_ERROR": "There was an error creating a meeting link, please try again"
|
||||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "حذف",
|
||||
"API": {
|
||||
|
||||
@@ -1,78 +1,78 @@
|
||||
{
|
||||
"MACROS": {
|
||||
"HEADER": "Macros",
|
||||
"HEADER_BTN_TXT": "Add a new macro",
|
||||
"HEADER_BTN_TXT_SAVE": "Save macro",
|
||||
"LOADING": "Fetching macros",
|
||||
"SIDEBAR_TXT": "<p><b>Macros</b><p>A macro is a set of saved actions that help customer service agents easily complete tasks. The agents can define a set of actions like tagging a conversation with a label, sending an email transcript, updating a custom attribute, etc., and they can run these actions in a single click. When the agents run the macro, the actions would be performed sequentially in the order they are defined. Macros improve productivity and increase consistency in actions. </p><p>A macro can be helpful in 2 ways. </p><p><b>As an agent assist:</b> If an agent performs a set of actions multiple times, they can save it as a macro and execute all the actions together using a single click.</p><p><b>As an option to onboard a team member:</b> Every agent has to perform many different checks/actions during each conversation. Onboarding a new support team member will be easy if pre-defined macros are available on the account. Instead of describing each step in detail, the manager/team lead can point to the macros used in different scenarios.</p>",
|
||||
"ERROR": "Something went wrong. Please try again",
|
||||
"ORDER_INFO": "Macros will run in the order you add your actions. You can rearrange them by dragging them by the handle beside each node.",
|
||||
"HEADER": "ماكروس",
|
||||
"HEADER_BTN_TXT": "إضافة ماكرو جديد",
|
||||
"HEADER_BTN_TXT_SAVE": "حفظ الماكرو",
|
||||
"LOADING": "جاري جلب الماكروس",
|
||||
"SIDEBAR_TXT": "<p><b>الماكروس</b><p>الماكرو هو مجموعة من الإجراءات المحفوظة التي تساعد وكلاء خدمة العملاء على إكمال المهام بسهولة. يمكن للوكلاء تحديد مجموعة من الإجراءات مثل وضع علامة على محادثة مع تسمية، وإرسال نص بريد إلكتروني، وتحديث سمة مخصصة، إلخ. ويمكنهم تنفيذ هذه الإجراءات بنقرة واحدة. وعندما يدير الوكلاء الكلية، يتم تنفيذ الإجراءات بالتسلسل حسب الترتيب الذي تحدده. يحسن الماكرو الإنتاجية ويزيد الاتساق في الإجراءات. </p><p>يمكن أن يكون الماكرو مفيداً بطريقتين. </p><p><b>كوكيل يساعد:</b> إذا قام وكيل بمجموعة من الإجراءات عدة مرات، يمكنهم حفظه كماكلي وتنفيذ جميع الإجراءات معاً باستخدام نقرة واحدة.</p><p><b>كخيار للدخول إلى عضوية الفريق:</b> يجب على كل وكيل إجراء العديد من الشيكات والإجراءات المختلفة خلال كل محادثة. سيكون عضو فريق الدعم الجديد من أونبواردينغ سهلا إذا كان الماكرو المحدد مسبقا متاحا على الحساب. وبدلا من وصف كل خطوة بالتفصيل، يمكن للمدير/الفريق أن يشير إلى الكتلة الكلية المستخدمة في سيناريوهات مختلفة.</p>",
|
||||
"ERROR": "حدث خطأ ما. الرجاء المحاولة مرة أخرى",
|
||||
"ORDER_INFO": "سيتم تشغيل الماكرو بالترتيب الذي تضيفه إجراءاتك. يمكنك إعادة ترتيبهم بسحبهم بواسطة المعالج بجانب كل عقدة.",
|
||||
"ADD": {
|
||||
"FORM": {
|
||||
"NAME": {
|
||||
"LABEL": "Macro name",
|
||||
"PLACEHOLDER": "Enter a name for your macro",
|
||||
"ERROR": "Name is required for creating a macro"
|
||||
"LABEL": "اسم الماكرو",
|
||||
"PLACEHOLDER": "أدخل اسم الماكرو الخاص بك",
|
||||
"ERROR": "الاسم مطلوب لإنشاء الماكرو"
|
||||
},
|
||||
"ACTIONS": {
|
||||
"LABEL": "الإجراءات"
|
||||
}
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Macro added successfully",
|
||||
"ERROR_MESSAGE": "Unable to create macro, Please try again later"
|
||||
"SUCCESS_MESSAGE": "تمت إضافة الماكرو بنجاح",
|
||||
"ERROR_MESSAGE": "غير قادر على إنشاء الماكرو ، الرجاء المحاولة مرة أخرى لاحقاً"
|
||||
}
|
||||
},
|
||||
"LIST": {
|
||||
"TABLE_HEADER": [
|
||||
"الاسم",
|
||||
"Created by",
|
||||
"Last updated by",
|
||||
"Visibility"
|
||||
"تم إنشاؤها بواسطة",
|
||||
"آخر تحديث بواسطة",
|
||||
"الظهور"
|
||||
],
|
||||
"404": "No macros found"
|
||||
"404": "لم يتم العثور على الماكروس"
|
||||
},
|
||||
"DELETE": {
|
||||
"TOOLTIP": "Delete macro",
|
||||
"TOOLTIP": "حذف الماكرو",
|
||||
"CONFIRM": {
|
||||
"MESSAGE": "هل أنت متأكد من الحذف ",
|
||||
"YES": "نعم، احذف",
|
||||
"NO": "لا"
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Macro deleted successfully",
|
||||
"ERROR_MESSAGE": "There was an error deleting the macro. Please try again later"
|
||||
"SUCCESS_MESSAGE": "تم حذف الماكرو بنجاح",
|
||||
"ERROR_MESSAGE": "حدث خطأ أثناء حذف الماكرو. الرجاء المحاولة مرة أخرى في وقت لاحق"
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"TOOLTIP": "Edit macro",
|
||||
"TOOLTIP": "تعديل الماكرو",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Macro updated successfully",
|
||||
"ERROR_MESSAGE": "Could not update Macro, Please try again later"
|
||||
"SUCCESS_MESSAGE": "تم تحديث الماكرو بنجاح",
|
||||
"ERROR_MESSAGE": "تعذر تحديث الماكرو ، الرجاء المحاولة مرة أخرى لاحقاً"
|
||||
}
|
||||
},
|
||||
"EDITOR": {
|
||||
"START_FLOW": "Start Flow",
|
||||
"END_FLOW": "End Flow",
|
||||
"LOADING": "Fetching macro",
|
||||
"ADD_BTN_TOOLTIP": "Add new action",
|
||||
"DELETE_BTN_TOOLTIP": "Delete Action",
|
||||
"START_FLOW": "بداية الإجرائات",
|
||||
"END_FLOW": "نهاية الإجرائات",
|
||||
"LOADING": "جاري جلب الماكروس",
|
||||
"ADD_BTN_TOOLTIP": "إضافة إجراء جديد",
|
||||
"DELETE_BTN_TOOLTIP": "حذف الإجراء",
|
||||
"VISIBILITY": {
|
||||
"LABEL": "Macro Visibility",
|
||||
"LABEL": "الرؤية الخاصة بالماكرو",
|
||||
"GLOBAL": {
|
||||
"LABEL": "Public",
|
||||
"DESCRIPTION": "This macro is available publicly for all agents in this account."
|
||||
"LABEL": "عامة",
|
||||
"DESCRIPTION": "هذا الماكرو متاح بشكل عام لجميع الوكلاء في هذا الحساب."
|
||||
},
|
||||
"PERSONAL": {
|
||||
"LABEL": "Private",
|
||||
"DESCRIPTION": "This macro will be private to you and not be available to others."
|
||||
"LABEL": "خاص",
|
||||
"DESCRIPTION": "هذا الماكرو سيكون خاصا لك ولن يكون متاحا للآخرين."
|
||||
}
|
||||
}
|
||||
},
|
||||
"EXECUTE": {
|
||||
"BUTTON_TOOLTIP": "Execute",
|
||||
"PREVIEW": "Preview Macro",
|
||||
"EXECUTED_SUCCESSFULLY": "Macro executed successfully"
|
||||
"BUTTON_TOOLTIP": "تنفيذ",
|
||||
"PREVIEW": "معاينة الماكرو",
|
||||
"EXECUTED_SUCCESSFULLY": "تم تنفيذ الماكرو بنجاح"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,9 +58,20 @@
|
||||
"AUDIO_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "الإشعارات الصوتية",
|
||||
"NOTE": "تمكين التنبيهات الصوتية في لوحة التحكم للرسائل والمحادثات الجديدة.",
|
||||
"NONE": "لا شيء",
|
||||
"ASSIGNED": "المحادثات المسندة",
|
||||
"ALL_CONVERSATIONS": "كل المحادثات"
|
||||
"ALERT_TYPE": {
|
||||
"TITLE": "أحداث التنبيه:",
|
||||
"NONE": "لا شيء",
|
||||
"ASSIGNED": "المحادثات المسندة",
|
||||
"ALL_CONVERSATIONS": "كل المحادثات"
|
||||
},
|
||||
"DEFAULT_TONE": {
|
||||
"TITLE": "نغمة التنبيه:"
|
||||
},
|
||||
"CONDITIONS": {
|
||||
"TITLE": "شروط التنبيه:",
|
||||
"CONDITION_ONE": "إرسال تنبيهات صوتية فقط إذا كانت نافذة المتصفح غير نشطة",
|
||||
"CONDITION_TWO": "إرسال تنبيهات كل 30 ثانية حتى يتم قراءة جميع المحادثات المعينة"
|
||||
}
|
||||
},
|
||||
"EMAIL_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "إشعارات البريد الإلكتروني",
|
||||
@@ -104,8 +115,8 @@
|
||||
"مشغول",
|
||||
"غير متصل"
|
||||
],
|
||||
"SET_AVAILABILITY_SUCCESS": "Availability has been set successfully",
|
||||
"SET_AVAILABILITY_ERROR": "Couldn't set availability, please try again"
|
||||
"SET_AVAILABILITY_SUCCESS": "تم تعيين التوافر بنجاح",
|
||||
"SET_AVAILABILITY_ERROR": "تعذر تعيين التوافر، الرجاء المحاولة مرة أخرى"
|
||||
},
|
||||
"EMAIL": {
|
||||
"LABEL": "عنوان البريد الإلكتروني الخاص بك",
|
||||
@@ -136,6 +147,7 @@
|
||||
"SELECTOR_SUBTITLE": "اختر حساباً من القائمة التالية",
|
||||
"PROFILE_SETTINGS": "إعدادات الملف الشخصي",
|
||||
"KEYBOARD_SHORTCUTS": "اختصارات لوحة المفاتيح",
|
||||
"SUPER_ADMIN_CONSOLE": "وحدة تحكم المدير المتميز",
|
||||
"LOGOUT": "تسجيل الخروج"
|
||||
},
|
||||
"APP_GLOBAL": {
|
||||
@@ -161,7 +173,7 @@
|
||||
"UPLOADING": "جاري الرفع..."
|
||||
},
|
||||
"LOCATION_BUBBLE": {
|
||||
"SEE_ON_MAP": "See on map"
|
||||
"SEE_ON_MAP": "مشاهدة على الخريطة"
|
||||
},
|
||||
"FORM_BUBBLE": {
|
||||
"SUBMIT": "إرسال"
|
||||
@@ -185,7 +197,7 @@
|
||||
"CONTACTS": "جهات الاتصال",
|
||||
"HOME": "الرئيسية",
|
||||
"AGENTS": "موظف الدعم",
|
||||
"AGENT_BOTS": "Bots",
|
||||
"AGENT_BOTS": "البوتات",
|
||||
"INBOXES": "قنوات التواصل",
|
||||
"NOTIFICATIONS": "الإشعارات",
|
||||
"CANNED_RESPONSES": "الردود السريعة",
|
||||
@@ -196,7 +208,7 @@
|
||||
"LABELS": "الوسوم",
|
||||
"CUSTOM_ATTRIBUTES": "سمات مخصصة",
|
||||
"AUTOMATION": "الأتمتة",
|
||||
"MACROS": "Macros",
|
||||
"MACROS": "ماكروس",
|
||||
"TEAMS": "الفرق",
|
||||
"BILLING": "الفواتير",
|
||||
"CUSTOM_VIEWS_FOLDER": "المجلدات",
|
||||
@@ -229,8 +241,8 @@
|
||||
"CATEGORY_EMPTY_MESSAGE": "لم يتم العثور على فئات"
|
||||
},
|
||||
"SET_AUTO_OFFLINE": {
|
||||
"TEXT": "Mark offline automatically",
|
||||
"INFO_TEXT": "Let the system automatically mark you offline when you aren't using the app or dashboard."
|
||||
"TEXT": "وضع علامة غير متصل تلقائيا",
|
||||
"INFO_TEXT": "السماح للنظام بوضع علامة غير متصل أوتوماتيكياً عندما لا تستخدم التطبيق أو لوحة التحكم."
|
||||
},
|
||||
"DOCS": "قراءة المستندات"
|
||||
},
|
||||
@@ -270,6 +282,7 @@
|
||||
}
|
||||
},
|
||||
"KEYBOARD_SHORTCUTS": {
|
||||
"TOGGLE_MODAL": "عرض جميع الاختصارات",
|
||||
"TITLE": {
|
||||
"OPEN_CONVERSATION": "فتح المحادثة",
|
||||
"RESOLVE_AND_NEXT": "حل وانتقل إلى التالي",
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"REGISTER": {
|
||||
"TRY_WOOT": "تسجيل حساب",
|
||||
"TITLE": "تسجيل",
|
||||
"TESTIMONIAL_HEADER": "All it takes is one step to move forward",
|
||||
"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>",
|
||||
"COMPANY_NAME": {
|
||||
|
||||
@@ -24,8 +24,11 @@
|
||||
"TITLE": "Select an agent bot",
|
||||
"DESC": "You can set an agent bot from the list to this inbox. The bot can initially handle the conversation and transfer it to an agent when needed.",
|
||||
"SUBMIT": "Обновяване",
|
||||
"DISCONNECT": "Disconnect Bot",
|
||||
"SUCCESS_MESSAGE": "Successfully updated the agent bot",
|
||||
"DISCONNECTED_SUCCESS_MESSAGE": "Successfully disconnected the agent bot",
|
||||
"ERROR_MESSAGE": "Could not update the agent bot, please try again later",
|
||||
"DISCONNECTED_ERROR_MESSAGE": "Could not disconnect the agent bot, please try again later",
|
||||
"SELECT_PLACEHOLDER": "Select Bot"
|
||||
},
|
||||
"ADD": {
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
}
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Custom Attribute added successfully",
|
||||
"ERROR_MESSAGE": "Could not able to create a custom attribute, Please try again later"
|
||||
"SUCCESS_MESSAGE": "Custom Attribute added successfully!",
|
||||
"ERROR_MESSAGE": "Could not create a Custom Attribute. Please try again later."
|
||||
}
|
||||
},
|
||||
"DELETE": {
|
||||
|
||||
@@ -53,6 +53,10 @@
|
||||
"ENABLE": "Create conversations from mentioned Tweets"
|
||||
}
|
||||
},
|
||||
"MICROSOFT": {
|
||||
"HELP": "To add your Microsoft account as a channel, you need to authenticate your Microsoft account by clicking on 'Sign in with Microsoft' ",
|
||||
"ERROR_MESSAGE": "There was an error connecting to Microsoft, please try again"
|
||||
},
|
||||
"WEBSITE_CHANNEL": {
|
||||
"TITLE": "Website channel",
|
||||
"DESC": "Create a channel for your website and start supporting your customers via our website widget.",
|
||||
@@ -548,6 +552,10 @@
|
||||
},
|
||||
"ENABLE_SSL": "Enable SSL"
|
||||
},
|
||||
"MICROSOFT": {
|
||||
"TITLE": "Microsoft",
|
||||
"SUBTITLE": "Reauthorize your MICROSOFT account"
|
||||
},
|
||||
"SMTP": {
|
||||
"TITLE": "SMTP",
|
||||
"SUBTITLE": "Set your SMTP details",
|
||||
|
||||
@@ -76,6 +76,13 @@
|
||||
"BODY": "<br/><p>Chatwoot will now sync all the incoming conversations into the <b><i>customer-conversations</i></b> channel inside your slack workplace.</p><p>Replying to a conversation thread in <b><i>customer-conversations</i></b> slack channel will create a response back to the customer through chatwoot.</p><p>Start the replies with <b><i>note:</i></b> to create private notes instead of replies.</p><p>If the replier on slack has an agent profile in chatwoot under the same email, the replies will be associated accordingly.</p><p>When the replier doesn't have an associated agent profile, the replies will be made from the bot profile.</p>"
|
||||
}
|
||||
},
|
||||
"DYTE": {
|
||||
"CLICK_HERE_TO_JOIN": "Click here to join",
|
||||
"LEAVE_THE_ROOM": "Leave the room",
|
||||
"START_VIDEO_CALL_HELP_TEXT": "Start a new video call with the customer",
|
||||
"JOIN_ERROR": "There was an error joining the call, please try again",
|
||||
"CREATE_ERROR": "There was an error creating a meeting link, please try again"
|
||||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Изтрий",
|
||||
"API": {
|
||||
|
||||
@@ -58,9 +58,20 @@
|
||||
"AUDIO_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "Audio Notifications",
|
||||
"NOTE": "Enable audio notifications in dashboard for new messages and conversations.",
|
||||
"NONE": "None",
|
||||
"ASSIGNED": "Assigned Conversations",
|
||||
"ALL_CONVERSATIONS": "All Conversations"
|
||||
"ALERT_TYPE": {
|
||||
"TITLE": "Alert events:",
|
||||
"NONE": "Нито един",
|
||||
"ASSIGNED": "Assigned Conversations",
|
||||
"ALL_CONVERSATIONS": "All Conversations"
|
||||
},
|
||||
"DEFAULT_TONE": {
|
||||
"TITLE": "Alert tone:"
|
||||
},
|
||||
"CONDITIONS": {
|
||||
"TITLE": "Alert conditions:",
|
||||
"CONDITION_ONE": "Send audio alerts only if the browser window is not active",
|
||||
"CONDITION_TWO": "Send alerts every 30s until all the assigned conversations are read"
|
||||
}
|
||||
},
|
||||
"EMAIL_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "Email Notifications",
|
||||
@@ -136,6 +147,7 @@
|
||||
"SELECTOR_SUBTITLE": "Select an account from the following list",
|
||||
"PROFILE_SETTINGS": "Profile Settings",
|
||||
"KEYBOARD_SHORTCUTS": "Keyboard Shortcuts",
|
||||
"SUPER_ADMIN_CONSOLE": "Super Admin Console",
|
||||
"LOGOUT": "Logout"
|
||||
},
|
||||
"APP_GLOBAL": {
|
||||
@@ -270,6 +282,7 @@
|
||||
}
|
||||
},
|
||||
"KEYBOARD_SHORTCUTS": {
|
||||
"TOGGLE_MODAL": "View all shortcuts",
|
||||
"TITLE": {
|
||||
"OPEN_CONVERSATION": "Open conversation",
|
||||
"RESOLVE_AND_NEXT": "Resolve and move to next",
|
||||
|
||||
@@ -24,8 +24,11 @@
|
||||
"TITLE": "Select an agent bot",
|
||||
"DESC": "You can set an agent bot from the list to this inbox. The bot can initially handle the conversation and transfer it to an agent when needed.",
|
||||
"SUBMIT": "Actualitza",
|
||||
"DISCONNECT": "Disconnect Bot",
|
||||
"SUCCESS_MESSAGE": "Successfully updated the agent bot",
|
||||
"DISCONNECTED_SUCCESS_MESSAGE": "Successfully disconnected the agent bot",
|
||||
"ERROR_MESSAGE": "Could not update the agent bot, please try again later",
|
||||
"DISCONNECTED_ERROR_MESSAGE": "Could not disconnect the agent bot, please try again later",
|
||||
"SELECT_PLACEHOLDER": "Select Bot"
|
||||
},
|
||||
"ADD": {
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
}
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Custom Attribute added successfully",
|
||||
"ERROR_MESSAGE": "Could not able to create a custom attribute, Please try again later"
|
||||
"SUCCESS_MESSAGE": "Custom Attribute added successfully!",
|
||||
"ERROR_MESSAGE": "Could not create a Custom Attribute. Please try again later."
|
||||
}
|
||||
},
|
||||
"DELETE": {
|
||||
|
||||
@@ -53,6 +53,10 @@
|
||||
"ENABLE": "Create conversations from mentioned Tweets"
|
||||
}
|
||||
},
|
||||
"MICROSOFT": {
|
||||
"HELP": "To add your Microsoft account as a channel, you need to authenticate your Microsoft account by clicking on 'Sign in with Microsoft' ",
|
||||
"ERROR_MESSAGE": "There was an error connecting to Microsoft, please try again"
|
||||
},
|
||||
"WEBSITE_CHANNEL": {
|
||||
"TITLE": "Canal Web",
|
||||
"DESC": "Crea un canal per al vostre lloc web i comença a donar suport als vostres clients mitjançant el teu widget del lloc web.",
|
||||
@@ -548,6 +552,10 @@
|
||||
},
|
||||
"ENABLE_SSL": "Enable SSL"
|
||||
},
|
||||
"MICROSOFT": {
|
||||
"TITLE": "Microsoft",
|
||||
"SUBTITLE": "Reauthorize your MICROSOFT account"
|
||||
},
|
||||
"SMTP": {
|
||||
"TITLE": "SMTP",
|
||||
"SUBTITLE": "Set your SMTP details",
|
||||
|
||||
@@ -76,6 +76,13 @@
|
||||
"BODY": "<br/><p>Chatwoot will now sync all the incoming conversations into the <b><i>customer-conversations</i></b> channel inside your slack workplace.</p><p>Replying to a conversation thread in <b><i>customer-conversations</i></b> slack channel will create a response back to the customer through chatwoot.</p><p>Start the replies with <b><i>note:</i></b> to create private notes instead of replies.</p><p>If the replier on slack has an agent profile in chatwoot under the same email, the replies will be associated accordingly.</p><p>When the replier doesn't have an associated agent profile, the replies will be made from the bot profile.</p>"
|
||||
}
|
||||
},
|
||||
"DYTE": {
|
||||
"CLICK_HERE_TO_JOIN": "Click here to join",
|
||||
"LEAVE_THE_ROOM": "Leave the room",
|
||||
"START_VIDEO_CALL_HELP_TEXT": "Start a new video call with the customer",
|
||||
"JOIN_ERROR": "There was an error joining the call, please try again",
|
||||
"CREATE_ERROR": "There was an error creating a meeting link, please try again"
|
||||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Suprimeix",
|
||||
"API": {
|
||||
|
||||
@@ -58,9 +58,20 @@
|
||||
"AUDIO_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "Audio Notifications",
|
||||
"NOTE": "Enable audio notifications in dashboard for new messages and conversations.",
|
||||
"NONE": "None",
|
||||
"ASSIGNED": "Assigned Conversations",
|
||||
"ALL_CONVERSATIONS": "All Conversations"
|
||||
"ALERT_TYPE": {
|
||||
"TITLE": "Alert events:",
|
||||
"NONE": "Ningú",
|
||||
"ASSIGNED": "Assigned Conversations",
|
||||
"ALL_CONVERSATIONS": "All Conversations"
|
||||
},
|
||||
"DEFAULT_TONE": {
|
||||
"TITLE": "Alert tone:"
|
||||
},
|
||||
"CONDITIONS": {
|
||||
"TITLE": "Alert conditions:",
|
||||
"CONDITION_ONE": "Send audio alerts only if the browser window is not active",
|
||||
"CONDITION_TWO": "Send alerts every 30s until all the assigned conversations are read"
|
||||
}
|
||||
},
|
||||
"EMAIL_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "Notificacions per correu electrònic",
|
||||
@@ -136,6 +147,7 @@
|
||||
"SELECTOR_SUBTITLE": "Selecciona un compte de la llista següent",
|
||||
"PROFILE_SETTINGS": "Configuració del Perfil",
|
||||
"KEYBOARD_SHORTCUTS": "Keyboard Shortcuts",
|
||||
"SUPER_ADMIN_CONSOLE": "Super Admin Console",
|
||||
"LOGOUT": "Sortir"
|
||||
},
|
||||
"APP_GLOBAL": {
|
||||
@@ -270,6 +282,7 @@
|
||||
}
|
||||
},
|
||||
"KEYBOARD_SHORTCUTS": {
|
||||
"TOGGLE_MODAL": "View all shortcuts",
|
||||
"TITLE": {
|
||||
"OPEN_CONVERSATION": "Open conversation",
|
||||
"RESOLVE_AND_NEXT": "Resolve and move to next",
|
||||
|
||||
@@ -24,8 +24,11 @@
|
||||
"TITLE": "Select an agent bot",
|
||||
"DESC": "You can set an agent bot from the list to this inbox. The bot can initially handle the conversation and transfer it to an agent when needed.",
|
||||
"SUBMIT": "Aktualizovat",
|
||||
"DISCONNECT": "Disconnect Bot",
|
||||
"SUCCESS_MESSAGE": "Successfully updated the agent bot",
|
||||
"DISCONNECTED_SUCCESS_MESSAGE": "Successfully disconnected the agent bot",
|
||||
"ERROR_MESSAGE": "Could not update the agent bot, please try again later",
|
||||
"DISCONNECTED_ERROR_MESSAGE": "Could not disconnect the agent bot, please try again later",
|
||||
"SELECT_PLACEHOLDER": "Select Bot"
|
||||
},
|
||||
"ADD": {
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
}
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Custom Attribute added successfully",
|
||||
"ERROR_MESSAGE": "Could not able to create a custom attribute, Please try again later"
|
||||
"SUCCESS_MESSAGE": "Custom Attribute added successfully!",
|
||||
"ERROR_MESSAGE": "Could not create a Custom Attribute. Please try again later."
|
||||
}
|
||||
},
|
||||
"DELETE": {
|
||||
|
||||
@@ -53,6 +53,10 @@
|
||||
"ENABLE": "Create conversations from mentioned Tweets"
|
||||
}
|
||||
},
|
||||
"MICROSOFT": {
|
||||
"HELP": "To add your Microsoft account as a channel, you need to authenticate your Microsoft account by clicking on 'Sign in with Microsoft' ",
|
||||
"ERROR_MESSAGE": "There was an error connecting to Microsoft, please try again"
|
||||
},
|
||||
"WEBSITE_CHANNEL": {
|
||||
"TITLE": "Kanál webové stránky",
|
||||
"DESC": "Vytvořte si kanál pro vaše webové stránky a začněte podporovat své zákazníky prostřednictvím našeho widgetu.",
|
||||
@@ -548,6 +552,10 @@
|
||||
},
|
||||
"ENABLE_SSL": "Enable SSL"
|
||||
},
|
||||
"MICROSOFT": {
|
||||
"TITLE": "Microsoft",
|
||||
"SUBTITLE": "Reauthorize your MICROSOFT account"
|
||||
},
|
||||
"SMTP": {
|
||||
"TITLE": "SMTP",
|
||||
"SUBTITLE": "Set your SMTP details",
|
||||
|
||||
@@ -76,6 +76,13 @@
|
||||
"BODY": "<br/><p>Chatwoot will now sync all the incoming conversations into the <b><i>customer-conversations</i></b> channel inside your slack workplace.</p><p>Replying to a conversation thread in <b><i>customer-conversations</i></b> slack channel will create a response back to the customer through chatwoot.</p><p>Start the replies with <b><i>note:</i></b> to create private notes instead of replies.</p><p>If the replier on slack has an agent profile in chatwoot under the same email, the replies will be associated accordingly.</p><p>When the replier doesn't have an associated agent profile, the replies will be made from the bot profile.</p>"
|
||||
}
|
||||
},
|
||||
"DYTE": {
|
||||
"CLICK_HERE_TO_JOIN": "Click here to join",
|
||||
"LEAVE_THE_ROOM": "Leave the room",
|
||||
"START_VIDEO_CALL_HELP_TEXT": "Start a new video call with the customer",
|
||||
"JOIN_ERROR": "There was an error joining the call, please try again",
|
||||
"CREATE_ERROR": "There was an error creating a meeting link, please try again"
|
||||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Vymazat",
|
||||
"API": {
|
||||
|
||||
@@ -58,9 +58,20 @@
|
||||
"AUDIO_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "Audio Notifications",
|
||||
"NOTE": "Enable audio notifications in dashboard for new messages and conversations.",
|
||||
"NONE": "Nic",
|
||||
"ASSIGNED": "Assigned Conversations",
|
||||
"ALL_CONVERSATIONS": "All Conversations"
|
||||
"ALERT_TYPE": {
|
||||
"TITLE": "Alert events:",
|
||||
"NONE": "Nic",
|
||||
"ASSIGNED": "Assigned Conversations",
|
||||
"ALL_CONVERSATIONS": "All Conversations"
|
||||
},
|
||||
"DEFAULT_TONE": {
|
||||
"TITLE": "Alert tone:"
|
||||
},
|
||||
"CONDITIONS": {
|
||||
"TITLE": "Alert conditions:",
|
||||
"CONDITION_ONE": "Send audio alerts only if the browser window is not active",
|
||||
"CONDITION_TWO": "Send alerts every 30s until all the assigned conversations are read"
|
||||
}
|
||||
},
|
||||
"EMAIL_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "E-mailová oznámení",
|
||||
@@ -136,6 +147,7 @@
|
||||
"SELECTOR_SUBTITLE": "Vyberte účet z následujícího seznamu",
|
||||
"PROFILE_SETTINGS": "Nastavení profilu",
|
||||
"KEYBOARD_SHORTCUTS": "Keyboard Shortcuts",
|
||||
"SUPER_ADMIN_CONSOLE": "Super Admin Console",
|
||||
"LOGOUT": "Odhlásit se"
|
||||
},
|
||||
"APP_GLOBAL": {
|
||||
@@ -270,6 +282,7 @@
|
||||
}
|
||||
},
|
||||
"KEYBOARD_SHORTCUTS": {
|
||||
"TOGGLE_MODAL": "View all shortcuts",
|
||||
"TITLE": {
|
||||
"OPEN_CONVERSATION": "Open conversation",
|
||||
"RESOLVE_AND_NEXT": "Resolve and move to next",
|
||||
|
||||
@@ -1,32 +1,35 @@
|
||||
{
|
||||
"AGENT_BOTS": {
|
||||
"HEADER": "Bots",
|
||||
"LOADING_EDITOR": "Loading Editor...",
|
||||
"LOADING_EDITOR": "Indlæser Editor...",
|
||||
"HEADER_BTN_TXT": "Add Bot Configuration",
|
||||
"SIDEBAR_TXT": "<p><b>Agent Bots</b> <p>Agent bots allows you to automate the conversations</p>",
|
||||
"CSML_BOT_EDITOR": {
|
||||
"NAME": {
|
||||
"LABEL": "Bot Name",
|
||||
"PLACEHOLDER": "Give your bot a name",
|
||||
"ERROR": "Bot name is required"
|
||||
"LABEL": "Bot navn",
|
||||
"PLACEHOLDER": "Giv din bot et navn",
|
||||
"ERROR": "Bot navn er påkrævet"
|
||||
},
|
||||
"DESCRIPTION": {
|
||||
"LABEL": "Bot Description",
|
||||
"PLACEHOLDER": "What does this bot do?"
|
||||
"LABEL": "Bot beskrivelse",
|
||||
"PLACEHOLDER": "Hvad gør denne bot?"
|
||||
},
|
||||
"BOT_CONFIG": {
|
||||
"ERROR": "Please enter your CSML bot configuration above",
|
||||
"API_ERROR": "Your CSML configuration is invalid, please fix it and try again."
|
||||
},
|
||||
"SUBMIT": "Validate and save"
|
||||
"SUBMIT": "Bekræft og gem"
|
||||
},
|
||||
"BOT_CONFIGURATION": {
|
||||
"TITLE": "Select an agent bot",
|
||||
"DESC": "You can set an agent bot from the list to this inbox. The bot can initially handle the conversation and transfer it to an agent when needed.",
|
||||
"SUBMIT": "Opdater",
|
||||
"DISCONNECT": "Disconnect Bot",
|
||||
"SUCCESS_MESSAGE": "Successfully updated the agent bot",
|
||||
"DISCONNECTED_SUCCESS_MESSAGE": "Successfully disconnected the agent bot",
|
||||
"ERROR_MESSAGE": "Could not update the agent bot, please try again later",
|
||||
"SELECT_PLACEHOLDER": "Select Bot"
|
||||
"DISCONNECTED_ERROR_MESSAGE": "Could not disconnect the agent bot, please try again later",
|
||||
"SELECT_PLACEHOLDER": "Vælg bot"
|
||||
},
|
||||
"ADD": {
|
||||
"TITLE": "Configure new bot",
|
||||
@@ -43,7 +46,7 @@
|
||||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Slet",
|
||||
"TITLE": "Delete Bot",
|
||||
"TITLE": "Slet bot",
|
||||
"SUBMIT": "Slet",
|
||||
"CANCEL_BUTTON_TEXT": "Annuller",
|
||||
"DESCRIPTION": "Are you sure you want to delete this bot? This action is irreversible",
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
}
|
||||
},
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Brugerdefineret attribut blev tilføjet",
|
||||
"ERROR_MESSAGE": "Kunne ikke oprette en brugerdefineret attribut. Prøv igen senere"
|
||||
"SUCCESS_MESSAGE": "Brugerdefineret attribut blev tilføjet!",
|
||||
"ERROR_MESSAGE": "Could not create a Custom Attribute. Please try again later."
|
||||
}
|
||||
},
|
||||
"DELETE": {
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
"TEAM_SELECT_LABEL": "Vælg Team",
|
||||
"NONE": "Ingen",
|
||||
"NO_TEAMS_AVAILABLE": "There are no teams added to this account yet.",
|
||||
"ASSIGN_SELECTED_TEAMS": "Assign selected team",
|
||||
"ASSIGN_SELECTED_TEAMS": "Tildel det valgte team",
|
||||
"ASSIGN_SUCCESFUL": "Teams assiged successfully",
|
||||
"ASSIGN_FAILED": "Failed to assign team, please try again"
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@
|
||||
"RATING_TITLE": "Bedømmelse",
|
||||
"FEEDBACK_TITLE": "Tilbagemelding",
|
||||
"CARD": {
|
||||
"SHOW_LABELS": "Show labels",
|
||||
"HIDE_LABELS": "Hide labels"
|
||||
"SHOW_LABELS": "Vis etiketter",
|
||||
"HIDE_LABELS": "Skjul etiketter"
|
||||
},
|
||||
"HEADER": {
|
||||
"RESOLVE_ACTION": "Løs",
|
||||
@@ -68,7 +68,7 @@
|
||||
"CARD_CONTEXT_MENU": {
|
||||
"PENDING": "Markér som afventende",
|
||||
"RESOLVED": "Marker som løst",
|
||||
"MARK_AS_UNREAD": "Mark as unread",
|
||||
"MARK_AS_UNREAD": "Marker som ulæst",
|
||||
"REOPEN": "Genåbn samtale",
|
||||
"SNOOZE": {
|
||||
"TITLE": "Udsæt",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"EMOJI": {
|
||||
"PLACEHOLDER": "Search emojis",
|
||||
"NOT_FOUND": "No emoji match your search"
|
||||
"PLACEHOLDER": "Søg emojis",
|
||||
"NOT_FOUND": "Ingen emoji matcher din søgning"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"ENTER_TO_SELECT": "Tryk enter for at vælge",
|
||||
"ENTER_TO_REMOVE": "Tryk enter for at fjerne",
|
||||
"SELECT_ONE": "Vælg en",
|
||||
"SELECT": "Select"
|
||||
"SELECT": "Vælg"
|
||||
}
|
||||
},
|
||||
"NOTIFICATIONS_PAGE": {
|
||||
@@ -139,6 +139,6 @@
|
||||
}
|
||||
},
|
||||
"DASHBOARD_APPS": {
|
||||
"LOADING_MESSAGE": "Loading Dashboard App..."
|
||||
"LOADING_MESSAGE": "Indlæser Dashboard App..."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,14 +217,14 @@
|
||||
"DOMAIN": {
|
||||
"LABEL": "Tilpasset Domæne",
|
||||
"PLACEHOLDER": "Portal brugerdefineret domæne",
|
||||
"HELP_TEXT": "Add only If you want to use a custom domain for your portals. Eg: https://example.com",
|
||||
"ERROR": "Enter a valid domain URL"
|
||||
"HELP_TEXT": "Tilføj kun hvis du vil bruge et brugerdefineret domæne, til dine portaler. Eksempelvis https://example.com",
|
||||
"ERROR": "Indtast et gyldigt domæne URL"
|
||||
},
|
||||
"HOME_PAGE_LINK": {
|
||||
"LABEL": "Link Til Hjemmeside",
|
||||
"PLACEHOLDER": "Link til portalens hjemmeside",
|
||||
"HELP_TEXT": "The link used to return from the portal to the home page. Eg: https://example.com",
|
||||
"ERROR": "Enter a valid home page URL"
|
||||
"ERROR": "Indtast en gyldig URL til startsiden"
|
||||
},
|
||||
"THEME_COLOR": {
|
||||
"LABEL": "Portal tema farve",
|
||||
|
||||
@@ -53,6 +53,10 @@
|
||||
"ENABLE": "Opret samtaler fra nævnte Tweets"
|
||||
}
|
||||
},
|
||||
"MICROSOFT": {
|
||||
"HELP": "To add your Microsoft account as a channel, you need to authenticate your Microsoft account by clicking on 'Sign in with Microsoft' ",
|
||||
"ERROR_MESSAGE": "There was an error connecting to Microsoft, please try again"
|
||||
},
|
||||
"WEBSITE_CHANNEL": {
|
||||
"TITLE": "Hjemmesidekanal",
|
||||
"DESC": "Opret en kanal til din hjemmeside og begynde at supporte dine kunder via vores hjemmeside widget.",
|
||||
@@ -239,9 +243,9 @@
|
||||
},
|
||||
"API_CALLBACK": {
|
||||
"TITLE": "Callback URL",
|
||||
"SUBTITLE": "You have to configure the webhook URL and the verification token in the Facebook Developer portal with the values shown below.",
|
||||
"SUBTITLE": "Du skal konfigurere dit webhook URL og verifikations token, i Facebook Developer portal med værdierne vist nedenfor.",
|
||||
"WEBHOOK_URL": "Webhook URL",
|
||||
"WEBHOOK_VERIFICATION_TOKEN": "Webhook Verification Token"
|
||||
"WEBHOOK_VERIFICATION_TOKEN": "Webhook verifikations token"
|
||||
},
|
||||
"SUBMIT_BUTTON": "Opret WhatsApp Kanal",
|
||||
"API": {
|
||||
@@ -421,7 +425,7 @@
|
||||
"PRE_CHAT_FORM": "Pre Chat Form",
|
||||
"BUSINESS_HOURS": "Forretningstider",
|
||||
"WIDGET_BUILDER": "Widget Builder",
|
||||
"BOT_CONFIGURATION": "Bot Configuration"
|
||||
"BOT_CONFIGURATION": "Bot konfiguration"
|
||||
},
|
||||
"SETTINGS": "Indstillinger",
|
||||
"FEATURES": {
|
||||
@@ -548,6 +552,10 @@
|
||||
},
|
||||
"ENABLE_SSL": "Aktiver SSL"
|
||||
},
|
||||
"MICROSOFT": {
|
||||
"TITLE": "Microsoft",
|
||||
"SUBTITLE": "Reauthorize your MICROSOFT account"
|
||||
},
|
||||
"SMTP": {
|
||||
"TITLE": "Smtp",
|
||||
"SUBTITLE": "Indstil dine IMAP-oplysninger",
|
||||
|
||||
@@ -76,6 +76,13 @@
|
||||
"BODY": "<br/><p>Chatwoot vil nu synkronisere alle indgående samtaler ind i <b><i>kundesamtaler</i></b> kanalen i din slack arbejdsplads.</p><p>Svar på en samtaletråd i <b><i>kunde-samtaler</i></b> slack kanal vil skabe et svar tilbage til kunden gennem chatwoot.</p><p>Start svarene med <b><i>note:</i></b> for at oprette private noter i stedet for svar.</p><p>Hvis replikatoren på slack har en agentprofil i chatwoot under samme e-mail, vil svarene blive tilknyttet i overensstemmelse hermed.</p><p>Når replikatoren ikke har en tilknyttet agentprofil, vil svarene blive fremsat fra bot-profilen.</p>"
|
||||
}
|
||||
},
|
||||
"DYTE": {
|
||||
"CLICK_HERE_TO_JOIN": "Click here to join",
|
||||
"LEAVE_THE_ROOM": "Leave the room",
|
||||
"START_VIDEO_CALL_HELP_TEXT": "Start a new video call with the customer",
|
||||
"JOIN_ERROR": "There was an error joining the call, please try again",
|
||||
"CREATE_ERROR": "There was an error creating a meeting link, please try again"
|
||||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Slet",
|
||||
"API": {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"MACROS": {
|
||||
"HEADER": "Macros",
|
||||
"HEADER_BTN_TXT": "Add a new macro",
|
||||
"HEADER_BTN_TXT_SAVE": "Save macro",
|
||||
"HEADER_BTN_TXT_SAVE": "Gem macro",
|
||||
"LOADING": "Fetching macros",
|
||||
"SIDEBAR_TXT": "<p><b>Macros</b><p>A macro is a set of saved actions that help customer service agents easily complete tasks. The agents can define a set of actions like tagging a conversation with a label, sending an email transcript, updating a custom attribute, etc., and they can run these actions in a single click. When the agents run the macro, the actions would be performed sequentially in the order they are defined. Macros improve productivity and increase consistency in actions. </p><p>A macro can be helpful in 2 ways. </p><p><b>As an agent assist:</b> If an agent performs a set of actions multiple times, they can save it as a macro and execute all the actions together using a single click.</p><p><b>As an option to onboard a team member:</b> Every agent has to perform many different checks/actions during each conversation. Onboarding a new support team member will be easy if pre-defined macros are available on the account. Instead of describing each step in detail, the manager/team lead can point to the macros used in different scenarios.</p>",
|
||||
"ERROR": "Something went wrong. Please try again",
|
||||
@@ -26,9 +26,9 @@
|
||||
"LIST": {
|
||||
"TABLE_HEADER": [
|
||||
"Navn",
|
||||
"Created by",
|
||||
"Oprettet af",
|
||||
"Last updated by",
|
||||
"Visibility"
|
||||
"Synlighed"
|
||||
],
|
||||
"404": "No macros found"
|
||||
},
|
||||
@@ -64,7 +64,7 @@
|
||||
"DESCRIPTION": "This macro is available publicly for all agents in this account."
|
||||
},
|
||||
"PERSONAL": {
|
||||
"LABEL": "Private",
|
||||
"LABEL": "Privat",
|
||||
"DESCRIPTION": "This macro will be private to you and not be available to others."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,9 +20,9 @@
|
||||
"NOTE": "Din e-mailadresse er din identitet og bruges til at logge ind."
|
||||
},
|
||||
"SEND_MESSAGE": {
|
||||
"TITLE": "Hotkey to send messages",
|
||||
"TITLE": "Genvejstast til at sende beskeder",
|
||||
"NOTE": "You can select a hotkey (either Enter or Cmd/Ctrl+Enter) based on your preference of writing.",
|
||||
"UPDATE_SUCCESS": "Your settings have been updated successfully",
|
||||
"UPDATE_SUCCESS": "Dine indstillinger er blevet opdateret",
|
||||
"CARD": {
|
||||
"ENTER_KEY": {
|
||||
"HEADING": "Enter (↵)",
|
||||
@@ -58,9 +58,20 @@
|
||||
"AUDIO_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "Lyd Notifikationer",
|
||||
"NOTE": "Aktivér lydmeddelelser i dashboard for nye beskeder og samtaler.",
|
||||
"NONE": "Ingen",
|
||||
"ASSIGNED": "Tildelte Samtaler",
|
||||
"ALL_CONVERSATIONS": "Alle Samtaler"
|
||||
"ALERT_TYPE": {
|
||||
"TITLE": "Alert events:",
|
||||
"NONE": "Ingen",
|
||||
"ASSIGNED": "Tildelte Samtaler",
|
||||
"ALL_CONVERSATIONS": "Alle Samtaler"
|
||||
},
|
||||
"DEFAULT_TONE": {
|
||||
"TITLE": "Alert tone:"
|
||||
},
|
||||
"CONDITIONS": {
|
||||
"TITLE": "Alert conditions:",
|
||||
"CONDITION_ONE": "Send audio alerts only if the browser window is not active",
|
||||
"CONDITION_TWO": "Send alerts every 30s until all the assigned conversations are read"
|
||||
}
|
||||
},
|
||||
"EMAIL_NOTIFICATIONS_SECTION": {
|
||||
"TITLE": "E-Mail Notifikationer",
|
||||
@@ -136,6 +147,7 @@
|
||||
"SELECTOR_SUBTITLE": "Vælg en konto fra følgende liste",
|
||||
"PROFILE_SETTINGS": "Profilindstillinger",
|
||||
"KEYBOARD_SHORTCUTS": "Tastaturgenveje",
|
||||
"SUPER_ADMIN_CONSOLE": "Super Admin Console",
|
||||
"LOGOUT": "Log Ud"
|
||||
},
|
||||
"APP_GLOBAL": {
|
||||
@@ -161,7 +173,7 @@
|
||||
"UPLOADING": "Uploader..."
|
||||
},
|
||||
"LOCATION_BUBBLE": {
|
||||
"SEE_ON_MAP": "See on map"
|
||||
"SEE_ON_MAP": "Se på kort"
|
||||
},
|
||||
"FORM_BUBBLE": {
|
||||
"SUBMIT": "Send"
|
||||
@@ -270,6 +282,7 @@
|
||||
}
|
||||
},
|
||||
"KEYBOARD_SHORTCUTS": {
|
||||
"TOGGLE_MODAL": "View all shortcuts",
|
||||
"TITLE": {
|
||||
"OPEN_CONVERSATION": "Åbn samtale",
|
||||
"RESOLVE_AND_NEXT": "Løs og flyt til næste",
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
"REGISTER": {
|
||||
"TRY_WOOT": "Registrer en konto",
|
||||
"TITLE": "Registrer",
|
||||
"TESTIMONIAL_HEADER": "All it takes is one step to move forward",
|
||||
"TESTIMONIAL_CONTENT": "You're one step away from engaging your customers, retaining them and finding new ones.",
|
||||
"TESTIMONIAL_HEADER": "Alt, hvad der skal til, er blot et skridt for at komme videre",
|
||||
"TESTIMONIAL_CONTENT": "Du er et skridt fra at engagere dine kunder, fastholde dem og finde nye kunder.",
|
||||
"TERMS_ACCEPT": "Ved at tilmelde dig, accepterer du vores <a href=\"https://www.chatwoot.com/terms\">T & C</a> og <a href=\"https://www.chatwoot.com/privacy-policy\">Privatlivspolitik</a>",
|
||||
"COMPANY_NAME": {
|
||||
"LABEL": "Company name",
|
||||
"PLACEHOLDER": "Enter your company name. eg: Wayne Enterprises",
|
||||
"ERROR": "Company name is too short"
|
||||
"LABEL": "Firmanavn",
|
||||
"PLACEHOLDER": "Indtast dit firmanavn, fx: Wayne Enterprises",
|
||||
"ERROR": "Firmanavn er for kort"
|
||||
},
|
||||
"FULL_NAME": {
|
||||
"LABEL": "Fulde navn",
|
||||
@@ -18,7 +18,7 @@
|
||||
"EMAIL": {
|
||||
"LABEL": "Arbejde e-mail",
|
||||
"PLACEHOLDER": "Indtast din arbejdsmailadresse fx: bruce@wayne.enterprises",
|
||||
"ERROR": "Please enter a valid work email address"
|
||||
"ERROR": "Indtast venligst en gyldig arbejdsmail"
|
||||
},
|
||||
"PASSWORD": {
|
||||
"LABEL": "Adgangskode",
|
||||
|
||||
@@ -24,8 +24,11 @@
|
||||
"TITLE": "Agenten-Bot auswählen",
|
||||
"DESC": "Sie können einen Agenten-Bot aus der Liste in diesen Posteingang setzen. Der Bot kann die Unterhaltung anfangs bearbeiten und bei Bedarf an einen Agenten übertragen.",
|
||||
"SUBMIT": "Aktualisieren",
|
||||
"DISCONNECT": "Disconnect Bot",
|
||||
"SUCCESS_MESSAGE": "Agenten-Bot erfolgreich aktualisiert",
|
||||
"DISCONNECTED_SUCCESS_MESSAGE": "Successfully disconnected the agent bot",
|
||||
"ERROR_MESSAGE": "Konnte den Agenten-Bot nicht aktualisieren, bitte versuchen Sie es später erneut",
|
||||
"DISCONNECTED_ERROR_MESSAGE": "Could not disconnect the agent bot, please try again later",
|
||||
"SELECT_PLACEHOLDER": "Bot auswählen"
|
||||
},
|
||||
"ADD": {
|
||||
@@ -37,29 +40,29 @@
|
||||
}
|
||||
},
|
||||
"LIST": {
|
||||
"404": "No Bots found, you can create a bot by clicking the 'Configure new bot' Button ↗",
|
||||
"LOADING": "Fetching Bots...",
|
||||
"TYPE": "Bot Type"
|
||||
"404": "Keine Bots gefunden, Sie können einen Bot erstellen, indem Sie auf den 'Neuen Bot konfigurieren' Button klicken ↗",
|
||||
"LOADING": "Bots werden geladen...",
|
||||
"TYPE": "Bot-Typ"
|
||||
},
|
||||
"DELETE": {
|
||||
"BUTTON_TEXT": "Löschen",
|
||||
"TITLE": "Delete Bot",
|
||||
"TITLE": "Bot löschen",
|
||||
"SUBMIT": "Löschen",
|
||||
"CANCEL_BUTTON_TEXT": "Stornieren",
|
||||
"DESCRIPTION": "Are you sure you want to delete this bot? This action is irreversible",
|
||||
"DESCRIPTION": "Sind Sie sicher, dass Sie diesen Bot löschen wollen? Diese Aktion kann nicht rückgängig gemacht werden",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Bot deleted successfully",
|
||||
"SUCCESS_MESSAGE": "Bot erfolgreich gelöscht",
|
||||
"ERROR_MESSAGE": "Could not able to delete bot, Please try again later"
|
||||
}
|
||||
},
|
||||
"EDIT": {
|
||||
"BUTTON_TEXT": "Bearbeiten",
|
||||
"LOADING": "Fetching Bots...",
|
||||
"TITLE": "Edit Bot",
|
||||
"LOADING": "Bots werden geladen...",
|
||||
"TITLE": "Bot bearbeiten",
|
||||
"CANCEL_BUTTON_TEXT": "Stornieren",
|
||||
"API": {
|
||||
"SUCCESS_MESSAGE": "Bot updated successfully",
|
||||
"ERROR_MESSAGE": "Could not update bot, Please try again later"
|
||||
"SUCCESS_MESSAGE": "Bot erfolgreich aktualisiert",
|
||||
"ERROR_MESSAGE": "Bot konnte nicht aktualisiert werden, bitte versuchen Sie es später erneut"
|
||||
}
|
||||
},
|
||||
"TYPES": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user