Merge branch 'release/1.13.0'

This commit is contained in:
Sojan 2021-02-16 19:54:24 +05:30
commit ec1ddc06fd
533 changed files with 8856 additions and 3906 deletions

View File

@ -27,10 +27,10 @@ gem 'uglifier'
gem 'flag_shih_tzu' gem 'flag_shih_tzu'
# Random name generator for user names # Random name generator for user names
gem 'haikunator' gem 'haikunator'
# Template parsing safetly # Template parsing safely
gem 'liquid' gem 'liquid'
# Parse Markdown to HTML # Parse Markdown to HTML
gem 'redcarpet' gem 'commonmarker'
##-- for active storage --## ##-- for active storage --##
gem 'aws-sdk-s3', require: false gem 'aws-sdk-s3', require: false
@ -44,6 +44,8 @@ gem 'pg'
gem 'redis' gem 'redis'
gem 'redis-namespace' gem 'redis-namespace'
gem 'redis-rack-cache' gem 'redis-rack-cache'
# super fast record imports in bulk
gem 'activerecord-import'
##--- gems for server & infra configuration ---## ##--- gems for server & infra configuration ---##
gem 'dotenv-rails' gem 'dotenv-rails'

View File

@ -18,56 +18,58 @@ GEM
specs: specs:
action-cable-testing (0.6.1) action-cable-testing (0.6.1)
actioncable (>= 5.0) actioncable (>= 5.0)
actioncable (6.0.3.4) actioncable (6.0.3.5)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.5)
nio4r (~> 2.0) nio4r (~> 2.0)
websocket-driver (>= 0.6.1) websocket-driver (>= 0.6.1)
actionmailbox (6.0.3.4) actionmailbox (6.0.3.5)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.5)
activejob (= 6.0.3.4) activejob (= 6.0.3.5)
activerecord (= 6.0.3.4) activerecord (= 6.0.3.5)
activestorage (= 6.0.3.4) activestorage (= 6.0.3.5)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.5)
mail (>= 2.7.1) mail (>= 2.7.1)
actionmailer (6.0.3.4) actionmailer (6.0.3.5)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.5)
actionview (= 6.0.3.4) actionview (= 6.0.3.5)
activejob (= 6.0.3.4) activejob (= 6.0.3.5)
mail (~> 2.5, >= 2.5.4) mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
actionpack (6.0.3.4) actionpack (6.0.3.5)
actionview (= 6.0.3.4) actionview (= 6.0.3.5)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.5)
rack (~> 2.0, >= 2.0.8) rack (~> 2.0, >= 2.0.8)
rack-test (>= 0.6.3) rack-test (>= 0.6.3)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.0, >= 1.2.0) rails-html-sanitizer (~> 1.0, >= 1.2.0)
actiontext (6.0.3.4) actiontext (6.0.3.5)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.5)
activerecord (= 6.0.3.4) activerecord (= 6.0.3.5)
activestorage (= 6.0.3.4) activestorage (= 6.0.3.5)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.5)
nokogiri (>= 1.8.5) nokogiri (>= 1.8.5)
actionview (6.0.3.4) actionview (6.0.3.5)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.5)
builder (~> 3.1) builder (~> 3.1)
erubi (~> 1.4) erubi (~> 1.4)
rails-dom-testing (~> 2.0) rails-dom-testing (~> 2.0)
rails-html-sanitizer (~> 1.1, >= 1.2.0) rails-html-sanitizer (~> 1.1, >= 1.2.0)
activejob (6.0.3.4) activejob (6.0.3.5)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.5)
globalid (>= 0.3.6) globalid (>= 0.3.6)
activemodel (6.0.3.4) activemodel (6.0.3.5)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.5)
activerecord (6.0.3.4) activerecord (6.0.3.5)
activemodel (= 6.0.3.4) activemodel (= 6.0.3.5)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.5)
activestorage (6.0.3.4) activerecord-import (1.0.7)
actionpack (= 6.0.3.4) activerecord (>= 3.2)
activejob (= 6.0.3.4) activestorage (6.0.3.5)
activerecord (= 6.0.3.4) actionpack (= 6.0.3.5)
activejob (= 6.0.3.5)
activerecord (= 6.0.3.5)
marcel (~> 0.3.1) marcel (~> 0.3.1)
activesupport (6.0.3.4) activesupport (6.0.3.5)
concurrent-ruby (~> 1.0, >= 1.0.2) concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 0.7, < 2) i18n (>= 0.7, < 2)
minitest (~> 5.1) minitest (~> 5.1)
@ -145,7 +147,9 @@ GEM
coderay (1.1.3) coderay (1.1.3)
coercible (1.0.0) coercible (1.0.0)
descendants_tracker (~> 0.0.1) descendants_tracker (~> 0.0.1)
concurrent-ruby (1.1.7) commonmarker (0.21.1)
ruby-enum (~> 0.5)
concurrent-ruby (1.1.8)
connection_pool (2.2.3) connection_pool (2.2.3)
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
@ -181,7 +185,7 @@ GEM
dotenv (= 2.7.6) dotenv (= 2.7.6)
railties (>= 3.2) railties (>= 3.2)
equalizer (0.0.11) equalizer (0.0.11)
erubi (1.9.0) erubi (1.10.0)
et-orbi (1.2.4) et-orbi (1.2.4)
tzinfo tzinfo
execjs (2.7.0) execjs (2.7.0)
@ -257,7 +261,7 @@ GEM
mime-types (~> 3.0) mime-types (~> 3.0)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
httpclient (2.8.3) httpclient (2.8.3)
i18n (1.8.5) i18n (1.8.8)
concurrent-ruby (~> 1.0) concurrent-ruby (~> 1.0)
ice_nine (0.11.2) ice_nine (0.11.2)
inflecto (0.0.2) inflecto (0.0.2)
@ -294,7 +298,7 @@ GEM
listen (3.3.3) listen (3.3.3)
rb-fsevent (~> 0.10, >= 0.10.3) rb-fsevent (~> 0.10, >= 0.10.3)
rb-inotify (~> 0.9, >= 0.9.10) rb-inotify (~> 0.9, >= 0.9.10)
loofah (2.7.0) loofah (2.9.0)
crass (~> 1.0.2) crass (~> 1.0.2)
nokogiri (>= 1.5.9) nokogiri (>= 1.5.9)
mail (2.7.1) mail (2.7.1)
@ -311,7 +315,7 @@ GEM
mini_magick (4.10.1) mini_magick (4.10.1)
mini_mime (1.0.2) mini_mime (1.0.2)
mini_portile2 (2.5.0) mini_portile2 (2.5.0)
minitest (5.14.2) minitest (5.14.3)
momentjs-rails (2.20.1) momentjs-rails (2.20.1)
railties (>= 3.1) railties (>= 3.1)
msgpack (1.3.3) msgpack (1.3.3)
@ -321,8 +325,8 @@ GEM
net-http-persistent (4.0.0) net-http-persistent (4.0.0)
connection_pool (~> 2.2) connection_pool (~> 2.2)
netrc (0.11.0) netrc (0.11.0)
nio4r (2.5.4) nio4r (2.5.5)
nokogiri (1.11.0) nokogiri (1.11.1)
mini_portile2 (~> 2.5.0) mini_portile2 (~> 2.5.0)
racc (~> 1.4) racc (~> 1.4)
oauth (0.5.4) oauth (0.5.4)
@ -353,38 +357,37 @@ GEM
rack rack
rack-test (1.1.0) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rails (6.0.3.4) rails (6.0.3.5)
actioncable (= 6.0.3.4) actioncable (= 6.0.3.5)
actionmailbox (= 6.0.3.4) actionmailbox (= 6.0.3.5)
actionmailer (= 6.0.3.4) actionmailer (= 6.0.3.5)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.5)
actiontext (= 6.0.3.4) actiontext (= 6.0.3.5)
actionview (= 6.0.3.4) actionview (= 6.0.3.5)
activejob (= 6.0.3.4) activejob (= 6.0.3.5)
activemodel (= 6.0.3.4) activemodel (= 6.0.3.5)
activerecord (= 6.0.3.4) activerecord (= 6.0.3.5)
activestorage (= 6.0.3.4) activestorage (= 6.0.3.5)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.5)
bundler (>= 1.3.0) bundler (>= 1.3.0)
railties (= 6.0.3.4) railties (= 6.0.3.5)
sprockets-rails (>= 2.0.0) sprockets-rails (>= 2.0.0)
rails-dom-testing (2.0.3) rails-dom-testing (2.0.3)
activesupport (>= 4.2.0) activesupport (>= 4.2.0)
nokogiri (>= 1.6) nokogiri (>= 1.6)
rails-html-sanitizer (1.3.0) rails-html-sanitizer (1.3.0)
loofah (~> 2.3) loofah (~> 2.3)
railties (6.0.3.4) railties (6.0.3.5)
actionpack (= 6.0.3.4) actionpack (= 6.0.3.5)
activesupport (= 6.0.3.4) activesupport (= 6.0.3.5)
method_source method_source
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.20.3, < 2.0) thor (>= 0.20.3, < 2.0)
rainbow (3.0.0) rainbow (3.0.0)
rake (13.0.1) rake (13.0.3)
rb-fsevent (0.10.4) rb-fsevent (0.10.4)
rb-inotify (0.10.1) rb-inotify (0.10.1)
ffi (~> 1.0) ffi (~> 1.0)
redcarpet (3.5.1)
redis (4.2.1) redis (4.2.1)
redis-namespace (1.8.0) redis-namespace (1.8.0)
redis (>= 3.0.4) redis (>= 3.0.4)
@ -444,6 +447,8 @@ GEM
rubocop (>= 0.87.0) rubocop (>= 0.87.0)
rubocop-rspec (1.43.2) rubocop-rspec (1.43.2)
rubocop (~> 0.87) rubocop (~> 0.87)
ruby-enum (0.9.0)
i18n
ruby-progressbar (1.10.1) ruby-progressbar (1.10.1)
ruby2ruby (2.4.4) ruby2ruby (2.4.4)
ruby_parser (~> 3.1) ruby_parser (~> 3.1)
@ -520,7 +525,7 @@ GEM
inflecto inflecto
virtus virtus
telephone_number (1.4.9) telephone_number (1.4.9)
thor (1.0.1) thor (1.1.0)
thread_safe (0.3.6) thread_safe (0.3.6)
tilt (2.0.10) tilt (2.0.10)
time_diff (0.3.0) time_diff (0.3.0)
@ -532,7 +537,7 @@ GEM
nokogiri (>= 1.6, < 2.0) nokogiri (>= 1.6, < 2.0)
twitty (0.1.1) twitty (0.1.1)
oauth oauth
tzinfo (1.2.8) tzinfo (1.2.9)
thread_safe (~> 0.1) thread_safe (~> 0.1)
tzinfo-data (1.2020.1) tzinfo-data (1.2020.1)
tzinfo (>= 1.0.0) tzinfo (>= 1.0.0)
@ -575,13 +580,14 @@ GEM
websocket-extensions (>= 0.1.0) websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5) websocket-extensions (0.1.5)
wisper (2.0.0) wisper (2.0.0)
zeitwerk (2.4.1) zeitwerk (2.4.2)
PLATFORMS PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
action-cable-testing action-cable-testing
activerecord-import
acts-as-taggable-on acts-as-taggable-on
administrate administrate
annotate annotate
@ -595,6 +601,7 @@ DEPENDENCIES
bullet bullet
bundle-audit bundle-audit
byebug byebug
commonmarker
cypress-on-rails (~> 1.0) cypress-on-rails (~> 1.0)
database_cleaner database_cleaner
devise devise
@ -629,7 +636,6 @@ DEPENDENCIES
pundit pundit
rack-cors rack-cors
rails rails
redcarpet
redis redis
redis-namespace redis-namespace
redis-rack-cache redis-rack-cache

View File

@ -1,21 +1,14 @@
class Api::V1::Accounts::Contacts::ContactInboxesController < Api::V1::Accounts::BaseController class Api::V1::Accounts::Contacts::ContactInboxesController < Api::V1::Accounts::BaseController
before_action :ensure_contact before_action :ensure_contact
before_action :ensure_inbox, only: [:create] before_action :ensure_inbox, only: [:create]
before_action :validate_channel_type
def create def create
source_id = params[:source_id] || SecureRandom.uuid source_id = params[:source_id] || SecureRandom.uuid
@contact_inbox = ContactInbox.create(contact: @contact, inbox: @inbox, source_id: source_id) @contact_inbox = ContactInbox.create!(contact: @contact, inbox: @inbox, source_id: source_id)
end end
private private
def validate_channel_type
return if @inbox.channel_type == 'Channel::Api'
render json: { error: 'Contact Inbox creation is only allowed in API inboxes' }, status: :unprocessable_entity
end
def ensure_inbox def ensure_inbox
@inbox = Current.account.inboxes.find(params[:inbox_id]) @inbox = Current.account.inboxes.find(params[:inbox_id])
end end

View File

@ -22,6 +22,14 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
@contacts = fetch_contact_last_seen_at(contacts) @contacts = fetch_contact_last_seen_at(contacts)
end end
def import
ActiveRecord::Base.transaction do
import = Current.account.data_imports.create!(data_type: 'contacts')
import.import_file.attach(params[:import_file])
end
head :ok
end
# returns online contacts # returns online contacts
def active def active
contacts = Current.account.contacts.where(id: ::OnlineStatusTracker contacts = Current.account.contacts.where(id: ::OnlineStatusTracker
@ -46,7 +54,7 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
rescue ActiveRecord::RecordInvalid => e rescue ActiveRecord::RecordInvalid => e
render json: { render json: {
message: e.record.errors.full_messages.join(', '), message: e.record.errors.full_messages.join(', '),
contact: Contact.find_by(email: contact_params[:email]) contact: Current.account.contacts.find_by(email: contact_params[:email])
}, status: :unprocessable_entity }, status: :unprocessable_entity
end end

View File

@ -1,9 +1,20 @@
class Api::V1::Accounts::Conversations::AssignmentsController < Api::V1::Accounts::Conversations::BaseController class Api::V1::Accounts::Conversations::AssignmentsController < Api::V1::Accounts::Conversations::BaseController
# assign agent to a conversation # assigns agent/team to a conversation
def create def create
set_assignee
render json: @assignee
end
private
def set_assignee
# if params[:assignee_id] is not a valid id, it will set to nil, hence unassigning the conversation # if params[:assignee_id] is not a valid id, it will set to nil, hence unassigning the conversation
assignee = Current.account.users.find_by(id: params[:assignee_id]) if params.key?(:assignee_id)
@conversation.update_assignee(assignee) @assignee = Current.account.users.find_by(id: params[:assignee_id])
render json: assignee @conversation.update_assignee(@assignee)
elsif params.key?(:team_id)
@assignee = Current.account.teams.find_by(id: params[:team_id])
@conversation.update!(team: @assignee)
end
end end
end end

View File

@ -22,9 +22,10 @@ class Api::V1::Accounts::InboxMembersController < Api::V1::Accounts::BaseControl
# get the list of user_ids from params # get the list of user_ids from params
# the missing ones are the agents which are to be deleted from the inbox # the missing ones are the agents which are to be deleted from the inbox
# the new ones are the agents which are to be added to the inbox # the new ones are the agents which are to be added to the inbox
ActiveRecord::Base.transaction do
agents_to_be_added_ids.each { |user_id| @inbox.add_member(user_id) } agents_to_be_added_ids.each { |user_id| @inbox.add_member(user_id) }
agents_to_be_removed_ids.each { |user_id| @inbox.remove_member(user_id) } agents_to_be_removed_ids.each { |user_id| @inbox.remove_member(user_id) }
end
end end
def agents_to_be_added_ids def agents_to_be_added_ids

View File

@ -89,6 +89,8 @@ class Api::V1::Accounts::InboxesController < Api::V1::Accounts::BaseController
:webhook_url, :webhook_url,
:email, :email,
:reply_time, :reply_time,
:pre_chat_form_enabled,
{ pre_chat_form_options: [:pre_chat_message, :require_email] },
{ selected_feature_flags: [] } { selected_feature_flags: [] }
]) ])
end end

View File

@ -1,6 +1,7 @@
class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseController class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseController
protect_from_forgery with: :null_session RESULTS_PER_PAGE = 15
protect_from_forgery with: :null_session
before_action :fetch_notification, only: [:update] before_action :fetch_notification, only: [:update]
before_action :set_primary_actor, only: [:read_all] before_action :set_primary_actor, only: [:read_all]
before_action :set_current_page, only: [:index] before_action :set_current_page, only: [:index]
@ -8,17 +9,18 @@ class Api::V1::Accounts::NotificationsController < Api::V1::Accounts::BaseContro
def index def index
@unread_count = current_user.notifications.where(account_id: current_account.id, read_at: nil).count @unread_count = current_user.notifications.where(account_id: current_account.id, read_at: nil).count
@count = notifications.count @count = notifications.count
@notifications = notifications.page @current_page @notifications = notifications.page(@current_page).per(RESULTS_PER_PAGE)
end end
def read_all def read_all
# rubocop:disable Rails/SkipsModelValidations
if @primary_actor if @primary_actor
current_user.notifications.where(account_id: current_account.id, primary_actor: @primary_actor, read_at: nil) current_user.notifications.where(account_id: current_account.id, primary_actor: @primary_actor, read_at: nil)
.update(read_at: DateTime.now.utc) .update_all(read_at: DateTime.now.utc)
else else
current_user.notifications.where(account_id: current_account.id, read_at: nil).update(read_at: DateTime.now.utc) current_user.notifications.where(account_id: current_account.id, read_at: nil).update_all(read_at: DateTime.now.utc)
end end
# rubocop:enable Rails/SkipsModelValidations
head :ok head :ok
end end

View File

@ -7,17 +7,41 @@ class Api::V1::Accounts::TeamMembersController < Api::V1::Accounts::BaseControll
end end
def create def create
record = @team.team_members.find_or_create_by(user_id: params[:user_id]) ActiveRecord::Base.transaction do
@team_member = record.user @team_members = params[:user_ids].map { |user_id| @team.add_member(user_id) }
end
end
def update
ActiveRecord::Base.transaction do
members_to_be_added_ids.each { |user_id| @team.add_member(user_id) }
members_to_be_removed_ids.each { |user_id| @team.remove_member(user_id) }
end
@team_members = @team.members
render action: 'create'
end end
def destroy def destroy
@team.team_members.find_by(user_id: params[:user_id])&.destroy ActiveRecord::Base.transaction do
params[:user_ids].map { |user_id| @team.remove_member(user_id) }
end
head :ok head :ok
end end
private private
def members_to_be_added_ids
params[:user_ids] - current_members_ids
end
def members_to_be_removed_ids
current_members_ids - params[:user_ids]
end
def current_members_ids
@current_members_ids ||= @team.members.pluck(:id)
end
def fetch_team def fetch_team
@team = Current.account.teams.find(params[:team_id]) @team = Current.account.teams.find(params[:team_id])
end end

View File

@ -0,0 +1,29 @@
class Api::V1::Accounts::Twitter::AuthorizationsController < Api::V1::Accounts::BaseController
include TwitterConcern
before_action :check_authorization
def create
@response = twitter_client.request_oauth_token(url: twitter_callback_url)
if @response.status == '200'
::Redis::Alfred.setex(oauth_token, Current.account.id)
render json: { success: true, url: oauth_authorize_endpoint(oauth_token) }
else
render json: { success: false }, status: :unprocessable_entity
end
end
private
def oauth_token
parsed_body['oauth_token']
end
def oauth_authorize_endpoint(oauth_token)
"#{twitter_api_base_url}/oauth/authorize?oauth_token=#{oauth_token}"
end
def check_authorization
raise Pundit::NotAuthorizedError unless Current.account_user.administrator?
end
end

View File

@ -20,11 +20,7 @@ class Api::V1::Widget::BaseController < ApplicationController
end end
def auth_token_params def auth_token_params
@auth_token_params ||= ::Widget::TokenService.new(token: request.headers[header_name]).decode_token @auth_token_params ||= ::Widget::TokenService.new(token: request.headers['X-Auth-Token']).decode_token
end
def header_name
'X-Auth-Token'
end end
def set_web_widget def set_web_widget
@ -39,6 +35,50 @@ class Api::V1::Widget::BaseController < ApplicationController
@contact = @contact_inbox.contact @contact = @contact_inbox.contact
end end
def create_conversation
::Conversation.create!(conversation_params)
end
def inbox
@inbox ||= ::Inbox.find_by(id: auth_token_params[:inbox_id])
end
def conversation_params
# FIXME: typo referrer in additional attributes, will probably require a migration.
{
account_id: inbox.account_id,
inbox_id: inbox.id,
contact_id: @contact.id,
contact_inbox_id: @contact_inbox.id,
additional_attributes: {
browser: browser_params,
referer: permitted_params[:message][:referer_url],
initiated_at: timestamp_params
}
}
end
def update_contact(email)
contact_with_email = @current_account.contacts.find_by(email: email)
if contact_with_email
@contact = ::ContactMergeAction.new(
account: @current_account,
base_contact: contact_with_email,
mergee_contact: @contact
).perform
else
@contact.update!(email: email, name: contact_name)
end
end
def contact_email
permitted_params[:contact][:email].downcase
end
def contact_name
params[:contact][:name] || contact_email.split('@')[0]
end
def browser_params def browser_params
{ {
browser_name: browser.name, browser_name: browser.name,
@ -48,4 +88,19 @@ class Api::V1::Widget::BaseController < ApplicationController
platform_version: browser.platform.version platform_version: browser.platform.version
} }
end end
def timestamp_params
{ timestamp: permitted_params[:message][:timestamp] }
end
def message_params
{
account_id: conversation.account_id,
sender: @contact,
content: permitted_params[:message][:content],
inbox_id: conversation.inbox_id,
echo_id: permitted_params[:message][:echo_id],
message_type: :incoming
}
end
end end

View File

@ -12,7 +12,7 @@ class Api::V1::Widget::ContactsController < Api::V1::Widget::BaseController
def process_hmac def process_hmac
return if params[:identifier_hash].blank? return if params[:identifier_hash].blank?
raise StandardError, 'HMAC failed: Invalid Identifer Hash Provided' unless valid_hmac? raise StandardError, 'HMAC failed: Invalid Identifier Hash Provided' unless valid_hmac?
@contact_inbox.update(hmac_verified: true) @contact_inbox.update(hmac_verified: true)
end end

View File

@ -5,6 +5,14 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
@conversation = conversation @conversation = conversation
end end
def create
ActiveRecord::Base.transaction do
update_contact(contact_email) if @contact.email.blank? && contact_email.present?
@conversation = create_conversation
conversation.messages.create(message_params)
end
end
def update_last_seen def update_last_seen
head :ok && return if conversation.nil? head :ok && return if conversation.nil?
@ -43,6 +51,6 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
end end
def permitted_params def permitted_params
params.permit(:id, :typing_status, :website_token, :email) params.permit(:id, :typing_status, :website_token, :email, contact: [:name, :email], message: [:content, :referer_url, :timestamp, :echo_id])
end end
end end

View File

@ -39,44 +39,7 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
end end
def set_conversation def set_conversation
@conversation = ::Conversation.create!(conversation_params) if conversation.nil? @conversation = create_conversation if conversation.nil?
end
def message_params
{
account_id: conversation.account_id,
sender: @contact,
content: permitted_params[:message][:content],
inbox_id: conversation.inbox_id,
echo_id: permitted_params[:message][:echo_id],
message_type: :incoming
}
end
def conversation_params
# FIXME: typo referrer in additional attributes
# will probably require a migration.
{
account_id: inbox.account_id,
inbox_id: inbox.id,
contact_id: @contact.id,
contact_inbox_id: @contact_inbox.id,
additional_attributes: {
browser: browser_params,
referer: permitted_params[:message][:referer_url],
initiated_at: timestamp_params
}
}
end
def timestamp_params
{
timestamp: permitted_params[:message][:timestamp]
}
end
def inbox
@inbox ||= ::Inbox.find_by(id: auth_token_params[:inbox_id])
end end
def message_finder_params def message_finder_params
@ -90,36 +53,12 @@ class Api::V1::Widget::MessagesController < Api::V1::Widget::BaseController
@message_finder ||= MessageFinder.new(conversation, message_finder_params) @message_finder ||= MessageFinder.new(conversation, message_finder_params)
end end
def update_contact(email)
contact_with_email = @current_account.contacts.find_by(email: email)
if contact_with_email
@contact = ::ContactMergeAction.new(
account: @current_account,
base_contact: contact_with_email,
mergee_contact: @contact
).perform
else
@contact.update!(
email: email,
name: contact_name
)
end
end
def contact_email
permitted_params[:contact][:email].downcase
end
def contact_name
contact_email.split('@')[0]
end
def message_update_params def message_update_params
params.permit(message: [{ submitted_values: [:name, :title, :value] }]) params.permit(message: [{ submitted_values: [:name, :title, :value] }])
end end
def permitted_params def permitted_params
params.permit(:id, :before, :website_token, contact: [:email], message: [:content, :referer_url, :timestamp, :echo_id]) params.permit(:id, :before, :website_token, contact: [:name, :email], message: [:content, :referer_url, :timestamp, :echo_id])
end end
def set_message def set_message

View File

@ -0,0 +1,26 @@
module TwitterConcern
extend ActiveSupport::Concern
private
def parsed_body
@parsed_body ||= Rack::Utils.parse_nested_query(@response.raw_response.body)
end
def host
ENV.fetch('FRONTEND_URL', '')
end
def twitter_client
Twitty::Facade.new do |config|
config.consumer_key = ENV.fetch('TWITTER_CONSUMER_KEY', nil)
config.consumer_secret = ENV.fetch('TWITTER_CONSUMER_SECRET', nil)
config.base_url = twitter_api_base_url
config.environment = ENV.fetch('TWITTER_ENVIRONMENT', '')
end
end
def twitter_api_base_url
'https://api.twitter.com'
end
end

View File

@ -20,7 +20,8 @@ class DashboardController < ActionController::Base
'TERMS_URL', 'TERMS_URL',
'PRIVACY_URL', 'PRIVACY_URL',
'DISPLAY_MANIFEST', 'DISPLAY_MANIFEST',
'CREATE_NEW_ACCOUNT_FROM_DASHBOARD' 'CREATE_NEW_ACCOUNT_FROM_DASHBOARD',
'CHATWOOT_INBOX_TOKEN'
).merge( ).merge(
APP_VERSION: Chatwoot.config[:version] APP_VERSION: Chatwoot.config[:version]
) )

View File

@ -1,30 +0,0 @@
class Twitter::AuthorizationsController < Twitter::BaseController
def create
@response = twitter_client.request_oauth_token(url: twitter_callback_url)
if @response.status == '200'
::Redis::Alfred.setex(oauth_token, account.id)
redirect_to oauth_authorize_endpoint(oauth_token)
else
redirect_to app_new_twitter_inbox_url(account_id: account.id)
end
end
private
def oauth_token
parsed_body['oauth_token']
end
def user
@user ||= User.find_by(id: params[:user_id])
end
def account
@account ||= user.account
end
def oauth_authorize_endpoint(oauth_token)
"#{twitter_api_base_url}/oauth/authorize?oauth_token=#{oauth_token}"
end
end

View File

@ -1,24 +1,3 @@
class Twitter::BaseController < ApplicationController class Twitter::BaseController < ApplicationController
private include TwitterConcern
def parsed_body
@parsed_body ||= Rack::Utils.parse_nested_query(@response.raw_response.body)
end
def host
ENV.fetch('FRONTEND_URL', '')
end
def twitter_client
Twitty::Facade.new do |config|
config.consumer_key = ENV.fetch('TWITTER_CONSUMER_KEY', nil)
config.consumer_secret = ENV.fetch('TWITTER_CONSUMER_SECRET', nil)
config.base_url = twitter_api_base_url
config.environment = ENV.fetch('TWITTER_ENVIRONMENT', '')
end
end
def twitter_api_base_url
'https://api.twitter.com'
end
end end

View File

@ -1,4 +1,6 @@
class Twitter::CallbacksController < Twitter::BaseController class Twitter::CallbacksController < Twitter::BaseController
include TwitterConcern
def show def show
return redirect_to twitter_app_redirect_url if permitted_params[:denied] return redirect_to twitter_app_redirect_url if permitted_params[:denied]

View File

@ -22,10 +22,12 @@ class ConversationFinder
def perform def perform
set_inboxes set_inboxes
set_team
set_assignee_type set_assignee_type
find_all_conversations find_all_conversations
filter_by_status unless params[:q] filter_by_status unless params[:q]
filter_by_team if @team
filter_by_labels if params[:labels] filter_by_labels if params[:labels]
filter_by_query if params[:q] filter_by_query if params[:q]
@ -61,6 +63,10 @@ class ConversationFinder
@assignee_type = params[:assignee_type] @assignee_type = params[:assignee_type]
end end
def set_team
@team = current_account.teams.find(params[:team_id]) if params[:team_id]
end
def find_all_conversations def find_all_conversations
@conversations = current_account.conversations.where(inbox_id: @inbox_ids) @conversations = current_account.conversations.where(inbox_id: @inbox_ids)
end end
@ -87,6 +93,10 @@ class ConversationFinder
@conversations = @conversations.where(status: params[:status] || DEFAULT_STATUS) @conversations = @conversations.where(status: params[:status] || DEFAULT_STATUS)
end end
def filter_by_team
@conversations = @conversations.where(team: @team)
end
def filter_by_labels def filter_by_labels
@conversations = @conversations.tagged_with(params[:labels], any: true) @conversations = @conversations.tagged_with(params[:labels], any: true)
end end

View File

@ -9,6 +9,8 @@ module FileTypeHelper
'image/bmp' 'image/bmp'
].include?(content_type) ].include?(content_type)
return :audio if content_type.include?('audio/')
:file :file
end end
end end

View File

@ -0,0 +1,14 @@
/* global axios */
import ApiClient from '../ApiClient';
class TwitterClient extends ApiClient {
constructor() {
super('twitter', { accountScoped: true });
}
generateAuthorization() {
return axios.post(`${this.url}/authorization`);
}
}
export default new TwitterClient();

View File

@ -6,10 +6,11 @@ class ConversationApi extends ApiClient {
super('conversations', { accountScoped: true }); super('conversations', { accountScoped: true });
} }
get({ inboxId, status, assigneeType, page, labels }) { get({ inboxId, status, assigneeType, page, labels, teamId }) {
return axios.get(this.url, { return axios.get(this.url, {
params: { params: {
inbox_id: inboxId, inbox_id: inboxId,
team_id: teamId,
status, status,
assignee_type: assigneeType, assignee_type: assigneeType,
page, page,
@ -56,13 +57,14 @@ class ConversationApi extends ApiClient {
return axios.post(`${this.url}/${conversationId}/unmute`); return axios.post(`${this.url}/${conversationId}/unmute`);
} }
meta({ inboxId, status, assigneeType, labels }) { meta({ inboxId, status, assigneeType, labels, teamId }) {
return axios.get(`${this.url}/meta`, { return axios.get(`${this.url}/meta`, {
params: { params: {
inbox_id: inboxId, inbox_id: inboxId,
status, status,
assignee_type: assigneeType, assignee_type: assigneeType,
labels, labels,
team_id: teamId,
}, },
}); });
} }

View File

@ -0,0 +1,33 @@
/* global axios */
import ApiClient from './ApiClient';
class NotificationsAPI extends ApiClient {
constructor() {
super('notifications', { accountScoped: true });
}
get(page) {
return axios.get(`${this.url}?page=${page}`);
}
getNotifications(contactId) {
return axios.get(`${this.url}/${contactId}/notifications`);
}
getUnreadCount() {
return axios.get(`${this.url}/unread_count`);
}
read(primaryActorType, primaryActorId) {
return axios.post(`${this.url}/read_all`, {
primary_actor_type: primaryActorType,
primary_actor_id: primaryActorId,
});
}
readAll() {
return axios.post(`${this.url}/read_all`);
}
}
export default new NotificationsAPI();

View File

@ -0,0 +1,9 @@
import TwitterClient from '../../channel/twitterClient';
import ApiClient from '../../ApiClient';
describe('#TwitterClient', () => {
it('creates correct instance', () => {
expect(TwitterClient).toBeInstanceOf(ApiClient);
expect(TwitterClient).toHaveProperty('generateAuthorization');
});
});

View File

@ -0,0 +1,13 @@
import notifications from '../notifications';
import ApiClient from '../ApiClient';
describe('#NotificationAPI', () => {
it('creates correct instance', () => {
expect(notifications).toBeInstanceOf(ApiClient);
expect(notifications).toHaveProperty('get');
expect(notifications).toHaveProperty('getNotifications');
expect(notifications).toHaveProperty('getUnreadCount');
expect(notifications).toHaveProperty('read');
expect(notifications).toHaveProperty('readAll');
});
});

View File

@ -0,0 +1,15 @@
import teams from '../teams';
import ApiClient from '../ApiClient';
describe('#TeamsAPI', () => {
it('creates correct instance', () => {
expect(teams).toBeInstanceOf(ApiClient);
expect(teams).toHaveProperty('get');
expect(teams).toHaveProperty('show');
expect(teams).toHaveProperty('create');
expect(teams).toHaveProperty('update');
expect(teams).toHaveProperty('delete');
expect(teams).toHaveProperty('getAgents');
expect(teams).toHaveProperty('addAgents');
});
});

View File

@ -0,0 +1,20 @@
/* global axios */
import ApiClient from './ApiClient';
export class TeamsAPI extends ApiClient {
constructor() {
super('teams', { accountScoped: true });
}
getAgents({ teamId }) {
return axios.get(`${this.url}/${teamId}/team_members`);
}
addAgents({ teamId, agentsList }) {
return axios.post(`${this.url}/${teamId}/team_members`, {
user_ids: agentsList,
});
}
}
export default new TeamsAPI();

View File

@ -8,23 +8,6 @@
} }
.reply-box__top { .reply-box__top {
.canned {
@include elegant-card;
background: $color-white;
border-bottom: $space-small solid $color-white;
border-top: $space-small solid $color-white;
left: 0;
max-height: 14rem;
overflow: auto;
position: absolute;
width: 24rem;
z-index: 100;
.active a {
background: $color-woot;
}
}
.icon { .icon {
color: $medium-gray; color: $medium-gray;

View File

@ -49,8 +49,10 @@
margin-top: $space-micro; margin-top: $space-micro;
.inbox-icon { .inbox-icon {
$icon-top-space: -1px;
display: inline-block; display: inline-block;
margin-right: $space-micro; margin-right: $space-micro;
margin-top: $icon-top-space;
min-width: $space-normal; min-width: $space-normal;
text-align: center; text-align: center;
} }
@ -70,9 +72,9 @@
.dropdown-pane { .dropdown-pane {
@include elegant-card; @include elegant-card;
@include border-light; @include border-light;
bottom: 6rem;
display: block; display: block;
left: 18%; left: 5rem;
top: -110px;
visibility: visible; visibility: visible;
width: 80%; width: 80%;
z-index: 999; z-index: 999;
@ -118,42 +120,6 @@
line-height: $global-lineheight; line-height: $global-lineheight;
} }
.current-user {
@include flex;
align-items: center;
cursor: pointer;
flex-direction: row;
.current-user--data {
@include flex;
flex-direction: column;
.current-user--name {
font-size: $font-size-small;
font-weight: $font-weight-medium;
line-height: 1;
margin-bottom: $space-smaller;
margin-left: $space-one;
margin-top: $space-micro;
}
.current-user--role {
color: $color-gray;
font-size: $font-size-mini;
margin-bottom: $zero;
margin-left: $space-one;
text-transform: capitalize;
}
}
.current-user--options {
font-size: $font-size-big;
margin-bottom: auto;
margin-left: auto;
margin-top: auto;
}
}
.hamburger--menu { .hamburger--menu {
cursor: pointer; cursor: pointer;
display: none; display: none;

View File

@ -80,6 +80,10 @@ export default {
type: String, type: String,
default: '', default: '',
}, },
activeTeam: {
type: Object,
default: () => {},
},
}, },
data() { data() {
return { return {
@ -128,12 +132,16 @@ export default {
status: this.activeStatus, status: this.activeStatus,
page: this.currentPage + 1, page: this.currentPage + 1,
labels: this.label ? [this.label] : undefined, labels: this.label ? [this.label] : undefined,
teamId: this.activeTeam.name ? this.activeTeam.id : undefined,
}; };
}, },
pageTitle() { pageTitle() {
if (this.inbox.name) { if (this.inbox.name) {
return this.inbox.name; return this.inbox.name;
} }
if (this.activeTeam.name) {
return this.activeTeam.name;
}
if (this.label) { if (this.label) {
return `#${this.label}`; return `#${this.label}`;
} }
@ -162,6 +170,9 @@ export default {
}, },
}, },
watch: { watch: {
activeTeam() {
this.resetAndFetchData();
},
conversationInbox() { conversationInbox() {
this.resetAndFetchData(); this.resetAndFetchData();
}, },

View File

@ -3,7 +3,7 @@
<button class="button small button--copy-code" @click="onCopy"> <button class="button small button--copy-code" @click="onCopy">
{{ $t('COMPONENTS.CODE.BUTTON_TEXT') }} {{ $t('COMPONENTS.CODE.BUTTON_TEXT') }}
</button> </button>
<highlightjs :language="lang" :code="script" /> <highlightjs v-if="script" :language="lang" :code="script" />
</div> </div>
</template> </template>
@ -15,7 +15,7 @@ export default {
props: { props: {
script: { script: {
type: String, type: String,
required: true, default: '',
}, },
lang: { lang: {
type: String, type: String,

View File

@ -13,6 +13,11 @@
:key="item.toState" :key="item.toState"
:menu-item="item" :menu-item="item"
/> />
<sidebar-item
v-if="shouldShowTeams"
:key="teamSection.toState"
:menu-item="teamSection"
/>
<sidebar-item <sidebar-item
v-if="shouldShowInboxes" v-if="shouldShowInboxes"
:key="inboxSection.toState" :key="inboxSection.toState"
@ -30,173 +35,65 @@
<availability-status /> <availability-status />
</div> </div>
<div class="bottom-nav app-context-menu"> <div class="bottom-nav app-context-menu" @click="toggleOptions">
<transition name="menu-slide"> <agent-details @show-options="toggleOptions" />
<div <notification-bell />
v-if="showOptionsMenu" <span class="current-user--options icon ion-android-more-vertical" />
v-on-clickaway="showOptions" <options-menu
class="dropdown-pane top" :show="showOptionsMenu"
> @toggle-accounts="toggleAccountModal"
<ul class="vertical dropdown menu"> @show-support-chat-window="toggleSupportChatWindow"
<li v-if="showChangeAccountOption"> @close="toggleOptions"
<button
class="button clear change-accounts--button"
@click="changeAccount"
>
{{ $t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS') }}
</button>
</li>
<li>
<router-link :to="`/app/accounts/${accountId}/profile/settings`">
{{ $t('SIDEBAR_ITEMS.PROFILE_SETTINGS') }}
</router-link>
</li>
<li>
<a href="#" @click.prevent="logout()">
{{ $t('SIDEBAR_ITEMS.LOGOUT') }}
</a>
</li>
</ul>
</div>
</transition>
<div class="current-user" @click.prevent="showOptions()">
<thumbnail
:src="currentUser.avatar_url"
:username="currentUserAvailableName"
/>
<div class="current-user--data">
<h3 class="current-user--name">
{{ currentUserAvailableName }}
</h3>
<h5 v-if="currentRole" class="current-user--role">
{{ $t(`AGENT_MGMT.AGENT_TYPES.${currentRole.toUpperCase()}`) }}
</h5>
</div>
<span class="current-user--options icon ion-android-more-vertical" />
</div>
</div>
<woot-modal
:show="showAccountModal"
:on-close="onClose"
class="account-selector--modal"
>
<woot-modal-header
:header-title="$t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS')"
:header-content="$t('SIDEBAR_ITEMS.SELECTOR_SUBTITLE')"
/> />
<div </div>
v-for="account in currentUser.accounts"
:key="account.id"
class="account-selector"
>
<a :href="`/app/accounts/${account.id}/dashboard`">
<i v-if="account.id === accountId" class="ion ion-ios-checkmark" />
<label :for="account.name" class="account--details">
<div class="account--name">{{ account.name }}</div>
<div class="account--role">{{ account.role }}</div>
</label>
</a>
</div>
<div
v-if="globalConfig.createNewAccountFromDashboard"
class="modal-footer delete-item"
>
<button
class="button success large expanded nice"
@click="createAccount"
>
{{ $t('CREATE_ACCOUNT.NEW_ACCOUNT') }}
</button>
</div>
</woot-modal>
<woot-modal <account-selector
:show-account-modal="showAccountModal"
@close-account-modal="toggleAccountModal"
@show-create-account-modal="openCreateAccountModal"
/>
<add-account-modal
:show="showCreateAccountModal" :show="showCreateAccountModal"
:on-close="onCloseCreate" @close-account-create-modal="closeCreateAccountModal"
class="account-selector--modal" />
>
<div class="column content-box">
<woot-modal-header
:header-title="$t('CREATE_ACCOUNT.NEW_ACCOUNT')"
:header-content="$t('CREATE_ACCOUNT.SELECTOR_SUBTITLE')"
/>
<form class="row" @submit.prevent="addAccount()">
<div class="medium-12 columns">
<label :class="{ error: $v.accountName.$error }">
{{ $t('CREATE_ACCOUNT.FORM.NAME.LABEL') }}
<input
v-model.trim="accountName"
type="text"
:placeholder="$t('CREATE_ACCOUNT.FORM.NAME.PLACEHOLDER')"
@input="$v.accountName.$touch"
/>
</label>
</div>
<div class="modal-footer medium-12 columns">
<div class="medium-12 columns">
<woot-submit-button
:disabled="
$v.accountName.$invalid ||
$v.accountName.$invalid ||
uiFlags.isCreating
"
:button-text="$t('CREATE_ACCOUNT.FORM.SUBMIT')"
:loading="uiFlags.isCreating"
button-class="large expanded"
/>
</div>
</div>
</form>
</div>
</woot-modal>
</aside> </aside>
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import { mixin as clickaway } from 'vue-clickaway';
import adminMixin from '../../mixins/isAdmin'; import adminMixin from '../../mixins/isAdmin';
import Auth from '../../api/auth';
import SidebarItem from './SidebarItem'; import SidebarItem from './SidebarItem';
import AvailabilityStatus from './AvailabilityStatus'; import AvailabilityStatus from './AvailabilityStatus';
import { frontendURL } from '../../helper/URLHelper'; import { frontendURL } from '../../helper/URLHelper';
import Thumbnail from '../widgets/Thumbnail';
import { getSidebarItems } from '../../i18n/default-sidebar'; import { getSidebarItems } from '../../i18n/default-sidebar';
import { required, minLength } from 'vuelidate/lib/validators';
import alertMixin from 'shared/mixins/alertMixin'; import alertMixin from 'shared/mixins/alertMixin';
import NotificationBell from './sidebarComponents/NotificationBell';
import AgentDetails from './sidebarComponents/AgentDetails.vue';
import OptionsMenu from './sidebarComponents/OptionsMenu.vue';
import AccountSelector from './sidebarComponents/AccountSelector.vue';
import AddAccountModal from './sidebarComponents/AddAccountModal.vue';
export default { export default {
components: { components: {
AgentDetails,
SidebarItem, SidebarItem,
Thumbnail,
AvailabilityStatus, AvailabilityStatus,
NotificationBell,
OptionsMenu,
AccountSelector,
AddAccountModal,
}, },
mixins: [clickaway, adminMixin, alertMixin], mixins: [adminMixin, alertMixin],
props: {
route: {
type: String,
default: '',
},
},
data() { data() {
return { return {
showOptionsMenu: false, showOptionsMenu: false,
showAccountModal: false, showAccountModal: false,
showCreateAccountModal: false, showCreateAccountModal: false,
accountName: '',
vertical: 'bottom',
horizontal: 'center',
}; };
}, },
validations: {
accountName: {
required,
minLength: minLength(1),
},
},
computed: { computed: {
...mapGetters({ ...mapGetters({
currentUser: 'getCurrentUser', currentUser: 'getCurrentUser',
@ -204,18 +101,10 @@ export default {
inboxes: 'inboxes/getInboxes', inboxes: 'inboxes/getInboxes',
accountId: 'getCurrentAccountId', accountId: 'getCurrentAccountId',
currentRole: 'getCurrentRole', currentRole: 'getCurrentRole',
uiFlags: 'agents/getUIFlags',
accountLabels: 'labels/getLabelsOnSidebar', accountLabels: 'labels/getLabelsOnSidebar',
teams: 'teams/getMyTeams',
}), }),
currentUserAvailableName() {
return this.currentUser.name;
},
showChangeAccountOption() {
if (this.globalConfig.createNewAccountFromDashboard) {
return true;
}
return this.currentUser.accounts.length > 1;
},
sidemenuItems() { sidemenuItems() {
return getSidebarItems(this.accountId); return getSidebarItems(this.accountId);
}, },
@ -242,6 +131,9 @@ export default {
shouldShowInboxes() { shouldShowInboxes() {
return this.sidemenuItems.common.routes.includes(this.currentRoute); return this.sidemenuItems.common.routes.includes(this.currentRoute);
}, },
shouldShowTeams() {
return this.shouldShowInboxes && this.teams.length;
},
inboxSection() { inboxSection() {
return { return {
icon: 'ion-folder', icon: 'ion-folder',
@ -281,15 +173,57 @@ export default {
})), })),
}; };
}, },
teamSection() {
return {
icon: 'ion-ios-people',
label: 'TEAMS',
hasSubMenu: true,
newLink: true,
key: 'team',
cssClass: 'menu-title align-justify teams-sidebar-menu',
toState: frontendURL(`accounts/${this.accountId}/settings/teams`),
toStateName: 'teams_list',
children: this.teams.map(team => ({
id: team.id,
label: team.name,
truncateLabel: true,
toState: frontendURL(`accounts/${this.accountId}/team/${team.id}`),
})),
};
},
dashboardPath() { dashboardPath() {
return frontendURL(`accounts/${this.accountId}/dashboard`); return frontendURL(`accounts/${this.accountId}/dashboard`);
}, },
}, },
watch: {
currentUser(newUserInfo, oldUserInfo) {
if (!oldUserInfo.email && newUserInfo.email) {
this.setChatwootUser();
}
},
},
mounted() { mounted() {
this.$store.dispatch('labels/get'); this.$store.dispatch('labels/get');
this.$store.dispatch('inboxes/get'); this.$store.dispatch('inboxes/get');
this.$store.dispatch('notifications/unReadCount');
this.$store.dispatch('teams/get');
this.setChatwootUser();
}, },
methods: { methods: {
toggleSupportChatWindow() {
window.$chatwoot.toggle();
},
setChatwootUser() {
if (!this.currentUser.email || !this.globalConfig.chatwootInboxToken) {
return;
}
window.$chatwoot.setUser(this.currentUser.email, {
name: this.currentUser.name,
email: this.currentUser.email,
avatar_url: this.currentUser.avatar_url,
identifier_hash: this.currentUser.hmac_identifier,
});
},
filterMenuItemsByRole(menuItems) { filterMenuItemsByRole(menuItems) {
if (!this.currentRole) { if (!this.currentRole) {
return []; return [];
@ -301,41 +235,19 @@ export default {
) > -1 ) > -1
); );
}, },
logout() { toggleOptions() {
Auth.logout();
},
showOptions() {
this.showOptionsMenu = !this.showOptionsMenu; this.showOptionsMenu = !this.showOptionsMenu;
}, },
changeAccount() { toggleAccountModal() {
this.showAccountModal = true; this.showAccountModal = !this.showAccountModal;
}, },
onClose() { openCreateAccountModal() {
this.showAccountModal = false;
},
createAccount() {
this.showAccountModal = false; this.showAccountModal = false;
this.showCreateAccountModal = true; this.showCreateAccountModal = true;
}, },
onCloseCreate() { closeCreateAccountModal() {
this.showCreateAccountModal = false; this.showCreateAccountModal = false;
}, },
async addAccount() {
try {
const account_id = await this.$store.dispatch('accounts/create', {
account_name: this.accountName,
});
this.onClose();
this.showAlert(this.$t('CREATE_ACCOUNT.API.SUCCESS_MESSAGE'));
window.location = `/app/accounts/${account_id}/dashboard`;
} catch (error) {
if (error.response.status === 422) {
this.showAlert(this.$t('CREATE_ACCOUNT.API.EXIST_MESSAGE'));
} else {
this.showAlert(this.$t('CREATE_ACCOUNT.API.ERROR_MESSAGE'));
}
}
},
}, },
}; };
</script> </script>
@ -406,7 +318,23 @@ export default {
} }
} }
} }
.app-context-menu { .app-context-menu {
align-items: center;
cursor: pointer;
display: flex;
flex-direction: row;
height: 6rem; height: 6rem;
} }
.current-user--options {
font-size: $font-size-big;
margin-bottom: auto;
margin-left: auto;
margin-top: auto;
}
.teams-sidebar-menu + .nested.vertical.menu {
padding-left: calc(var(--space-medium) - var(--space-one));
}
</style> </style>

View File

@ -155,7 +155,6 @@ export default {
.wrap { .wrap {
display: flex; display: flex;
align-items: center;
} }
.label-color--display { .label-color--display {

View File

@ -0,0 +1,56 @@
<template>
<woot-modal
:show="showAccountModal"
:on-close="() => $emit('close-account-modal')"
class="account-selector--modal"
>
<woot-modal-header
:header-title="$t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS')"
:header-content="$t('SIDEBAR_ITEMS.SELECTOR_SUBTITLE')"
/>
<div
v-for="account in currentUser.accounts"
:key="account.id"
class="account-selector"
>
<a :href="`/app/accounts/${account.id}/dashboard`">
<i v-if="account.id === accountId" class="ion ion-ios-checkmark" />
<label :for="account.name" class="account--details">
<div class="account--name">{{ account.name }}</div>
<div class="account--role">{{ account.role }}</div>
</label>
</a>
</div>
<div
v-if="globalConfig.createNewAccountFromDashboard"
class="modal-footer delete-item"
>
<button
class="button success large expanded nice"
@click="$emit('show-create-account-modal')"
>
{{ $t('CREATE_ACCOUNT.NEW_ACCOUNT') }}
</button>
</div>
</woot-modal>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
props: {
showAccountModal: {
type: Boolean,
default: true,
},
},
computed: {
...mapGetters({
accountId: 'getCurrentAccountId',
currentUser: 'getCurrentUser',
globalConfig: 'globalConfig/get',
}),
},
};
</script>

View File

@ -0,0 +1,92 @@
<template>
<woot-modal
:show="show"
:on-close="() => $emit('close-account-create-modal')"
class="account-selector--modal"
>
<div class="column content-box">
<woot-modal-header
:header-title="$t('CREATE_ACCOUNT.NEW_ACCOUNT')"
:header-content="$t('CREATE_ACCOUNT.SELECTOR_SUBTITLE')"
/>
<form class="row" @submit.prevent="addAccount">
<div class="medium-12 columns">
<label :class="{ error: $v.accountName.$error }">
{{ $t('CREATE_ACCOUNT.FORM.NAME.LABEL') }}
<input
v-model.trim="accountName"
type="text"
:placeholder="$t('CREATE_ACCOUNT.FORM.NAME.PLACEHOLDER')"
@input="$v.accountName.$touch"
/>
</label>
</div>
<div class="modal-footer medium-12 columns">
<div class="medium-12 columns">
<woot-submit-button
:disabled="
$v.accountName.$invalid ||
$v.accountName.$invalid ||
uiFlags.isCreating
"
:button-text="$t('CREATE_ACCOUNT.FORM.SUBMIT')"
:loading="uiFlags.isCreating"
button-class="large expanded"
/>
</div>
</div>
</form>
</div>
</woot-modal>
</template>
<script>
import { required, minLength } from 'vuelidate/lib/validators';
import { mapGetters } from 'vuex';
import alertMixin from 'shared/mixins/alertMixin';
export default {
mixins: [alertMixin],
props: {
show: {
type: Boolean,
default: false,
},
},
data() {
return {
accountName: '',
};
},
validations: {
accountName: {
required,
minLength: minLength(1),
},
},
computed: {
...mapGetters({
uiFlags: 'agents/getUIFlags',
}),
},
methods: {
async addAccount() {
try {
const account_id = await this.$store.dispatch('accounts/create', {
account_name: this.accountName,
});
this.$emit('close-account-create-modal');
this.showAlert(this.$t('CREATE_ACCOUNT.API.SUCCESS_MESSAGE'));
window.location = `/app/accounts/${account_id}/dashboard`;
} catch (error) {
if (error.response.status === 422) {
this.showAlert(this.$t('CREATE_ACCOUNT.API.EXIST_MESSAGE'));
} else {
this.showAlert(this.$t('CREATE_ACCOUNT.API.ERROR_MESSAGE'));
}
}
},
},
};
</script>

View File

@ -0,0 +1,63 @@
<template>
<div class="current-user--row">
<thumbnail
:src="currentUser.avatar_url"
:username="currentUserAvailableName"
/>
<div class="current-user--data">
<h3 class="current-user--name text-truncate">
{{ currentUserAvailableName }}
</h3>
<h5 v-if="currentRole" class="current-user--role">
{{ $t(`AGENT_MGMT.AGENT_TYPES.${currentRole.toUpperCase()}`) }}
</h5>
</div>
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import Thumbnail from '../../widgets/Thumbnail';
export default {
components: {
Thumbnail,
},
computed: {
...mapGetters({
currentUser: 'getCurrentUser',
currentRole: 'getCurrentRole',
}),
currentUserAvailableName() {
return this.currentUser.name;
},
},
};
</script>
<style scoped lang="scss">
.current-user--row {
align-items: center;
display: flex;
}
.current-user--data {
display: flex;
flex-direction: column;
.current-user--name {
font-size: var(--font-size-small);
font-weight: var(--font-weight-medium);
margin-bottom: var(--space-micro);
margin-left: var(--space-one);
max-width: 12rem;
}
.current-user--role {
color: var(--color-gray);
font-size: var(--font-size-mini);
margin-bottom: var(--zero);
margin-left: var(--space-one);
text-transform: capitalize;
}
}
</style>

View File

@ -0,0 +1,52 @@
<template>
<span class="notifications icon ion-ios-bell" @click.stop="showNotification">
<span v-if="unreadCount" class="unread-badge">{{ unreadCount }}</span>
</span>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
computed: {
...mapGetters({
accountId: 'getCurrentAccountId',
notificationMetadata: 'notifications/getMeta',
}),
unreadCount() {
if (!this.notificationMetadata.unreadCount) {
return 0;
}
return this.notificationMetadata.unreadCount < 100
? this.notificationMetadata.unreadCount
: '99+';
},
},
methods: {
showNotification() {
this.$router.push(`/app/accounts/${this.accountId}/notifications`);
},
},
};
</script>
<style scoped lang="scss">
.notifications {
font-size: var(--font-size-big);
margin-bottom: auto;
margin-left: auto;
margin-top: auto;
position: relative;
.unread-badge {
background: var(--r-300);
border-radius: var(--space-small);
color: var(--white);
font-size: var(--font-size-micro);
font-weight: var(--font-weight-black);
left: var(--space-slab);
padding: 0 var(--space-smaller);
position: absolute;
top: var(--space-smaller);
}
}
</style>

View File

@ -0,0 +1,76 @@
<template>
<transition name="menu-slide">
<div
v-if="show"
v-on-clickaway="() => $emit('close')"
class="dropdown-pane top"
>
<ul class="vertical dropdown menu">
<li v-if="showChangeAccountOption">
<button
class="button clear change-accounts--button"
@click="$emit('toggle-accounts')"
>
{{ $t('SIDEBAR_ITEMS.CHANGE_ACCOUNTS') }}
</button>
</li>
<li v-if="globalConfig.chatwootInboxToken">
<button
class="button clear change-accounts--button"
@click="$emit('show-support-chat-window')"
>
Contact Support
</button>
</li>
<li>
<router-link :to="`/app/accounts/${accountId}/profile/settings`">
{{ $t('SIDEBAR_ITEMS.PROFILE_SETTINGS') }}
</router-link>
</li>
<li>
<a href="#" @click.prevent="logout">
{{ $t('SIDEBAR_ITEMS.LOGOUT') }}
</a>
</li>
</ul>
</div>
</transition>
</template>
<script>
import { mixin as clickaway } from 'vue-clickaway';
import { mapGetters } from 'vuex';
import Auth from '../../../api/auth';
export default {
mixins: [clickaway],
props: {
show: {
type: Boolean,
default: false,
},
},
computed: {
...mapGetters({
currentUser: 'getCurrentUser',
globalConfig: 'globalConfig/get',
accountId: 'getCurrentAccountId',
}),
showChangeAccountOption() {
if (this.globalConfig.createNewAccountFromDashboard) {
return true;
}
return this.currentUser.accounts.length > 1;
},
},
methods: {
logout() {
Auth.logout();
if (this.globalConfig.chatwootInboxToken) {
window.$chatwoot.reset();
}
},
},
};
</script>

View File

@ -172,6 +172,7 @@ export default {
margin-left: -2px; margin-left: -2px;
font-size: var(--font-size-small); font-size: var(--font-size-small);
padding: var(--space-small) var(--space-normal); padding: var(--space-small) var(--space-normal);
border-radius: 0;
&:hover, &:hover,
&:focus, &:focus,

View File

@ -1,55 +1,113 @@
<template> <template>
<div ref="editor" class="editor-root"></div> <div class="editor-root">
<tag-agents
v-if="showUserMentions && isPrivate"
:search-key="mentionSearchKey"
@click="insertMentionNode"
/>
<div ref="editor"></div>
</div>
</template> </template>
<script> <script>
import { EditorState } from 'prosemirror-state';
import { EditorView } from 'prosemirror-view'; import { EditorView } from 'prosemirror-view';
import { import { defaultMarkdownSerializer } from 'prosemirror-markdown';
schema,
defaultMarkdownParser,
defaultMarkdownSerializer,
} from 'prosemirror-markdown';
import { wootWriterSetup } from '@chatwoot/prosemirror-schema';
const TYPING_INDICATOR_IDLE_TIME = 4000; const TYPING_INDICATOR_IDLE_TIME = 4000;
const createState = (content, placeholder) => import {
EditorState.create({ addMentionsToMarkdownSerializer,
doc: defaultMarkdownParser.parse(content), addMentionsToMarkdownParser,
plugins: wootWriterSetup({ schema, placeholder }), schemaWithMentions,
} from '@chatwoot/prosemirror-schema/src/mentions/schema';
import {
suggestionsPlugin,
triggerCharacters,
} from '@chatwoot/prosemirror-schema/src/mentions/plugin';
import TagAgents from '../conversation/TagAgents.vue';
import { EditorState } from 'prosemirror-state';
import { defaultMarkdownParser } from 'prosemirror-markdown';
import { wootWriterSetup } from '@chatwoot/prosemirror-schema';
const createState = (content, placeholder, plugins = []) => {
return EditorState.create({
doc: addMentionsToMarkdownParser(defaultMarkdownParser).parse(content),
plugins: wootWriterSetup({
schema: schemaWithMentions,
placeholder,
plugins,
}),
}); });
};
export default { export default {
name: 'WootMessageEditor', name: 'WootMessageEditor',
components: { TagAgents },
props: { props: {
value: { type: String, default: '' }, value: { type: String, default: '' },
placeholder: { type: String, default: '' }, placeholder: { type: String, default: '' },
isPrivate: { type: Boolean, default: false },
}, },
data() { data() {
return { return {
lastValue: null, lastValue: null,
showUserMentions: false,
mentionSearchKey: '',
editorView: null,
range: null,
}; };
}, },
computed: {
plugins() {
return [
suggestionsPlugin({
matcher: triggerCharacters('@'),
onEnter: args => {
this.showUserMentions = true;
this.range = args.range;
this.editorView = args.view;
return false;
},
onChange: args => {
this.editorView = args.view;
this.range = args.range;
this.mentionSearchKey = args.text.replace('@', '');
return false;
},
onExit: () => {
this.mentionSearchKey = '';
this.showUserMentions = false;
this.editorView = null;
return false;
},
onKeyDown: ({ event }) => {
return event.keyCode === 13 && this.showUserMentions;
},
}),
];
},
},
watch: { watch: {
showUserMentions(updatedValue) {
this.$emit('toggle-user-mention', this.isPrivate && updatedValue);
},
value(newValue) { value(newValue) {
if (newValue !== this.lastValue) { if (newValue !== this.lastValue) {
this.state = createState(newValue, this.placeholder); this.state = createState(newValue, this.placeholder, this.plugins);
this.view.updateState(this.state); this.view.updateState(this.state);
} }
}, },
}, },
created() { created() {
this.state = createState(this.value, this.placeholder); this.state = createState(this.value, this.placeholder, this.plugins);
}, },
mounted() { mounted() {
this.view = new EditorView(this.$refs.editor, { this.view = new EditorView(this.$refs.editor, {
state: this.state, state: this.state,
dispatchTransaction: tx => { dispatchTransaction: tx => {
this.state = this.state.apply(tx); this.state = this.state.apply(tx);
this.view.updateState(this.state); this.emitOnChange();
this.lastValue = defaultMarkdownSerializer.serialize(this.state.doc);
this.$emit('input', this.lastValue);
}, },
handleDOMEvents: { handleDOMEvents: {
keyup: () => { keyup: () => {
@ -65,6 +123,33 @@ export default {
}); });
}, },
methods: { methods: {
insertMentionNode(mentionItem) {
if (!this.view) {
return null;
}
const node = this.view.state.schema.nodes.mention.create({
userId: mentionItem.key,
userFullName: mentionItem.label,
});
const tr = this.view.state.tr.replaceWith(
this.range.from,
this.range.to,
node
);
this.state = this.view.state.apply(tr);
return this.emitOnChange();
},
emitOnChange() {
this.view.updateState(this.state);
this.lastValue = addMentionsToMarkdownSerializer(
defaultMarkdownSerializer
).serialize(this.state.doc);
this.$emit('input', this.lastValue);
},
hideMentions() {
this.showUserMentions = false;
},
resetTyping() { resetTyping() {
this.$emit('typing-off'); this.$emit('typing-off');
this.idleTimer = null; this.idleTimer = null;
@ -115,4 +200,14 @@ export default {
max-height: 12rem; max-height: 12rem;
overflow: auto; overflow: auto;
} }
.is-private {
.prosemirror-mention-node {
font-weight: var(--font-weight-medium);
background: var(--s-300);
border-radius: var(--border-radius-small);
padding: 1px 4px;
color: var(--white);
}
}
</style> </style>

View File

@ -22,7 +22,7 @@
</file-upload> </file-upload>
</button> </button>
<button <button
v-if="enableRichEditor" v-if="enableRichEditor && !isOnPrivateNote"
class="button clear button--emoji" class="button clear button--emoji"
:title="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')" :title="$t('CONVERSATION.REPLYBOX.TIP_FORMAT_ICON')"
@click="toggleFormatMode" @click="toggleFormatMode"
@ -31,6 +31,17 @@
</button> </button>
</div> </div>
<div class="right-wrap"> <div class="right-wrap">
<div v-if="isFormatMode" class="enter-to-send--checkbox">
<input
:checked="enterToSendEnabled"
type="checkbox"
value="enterToSend"
@input="toggleEnterToSend"
/>
<label for="enterToSend">
{{ $t('CONVERSATION.REPLYBOX.ENTER_TO_SEND') }}
</label>
</div>
<button <button
class="button nice primary button--send" class="button nice primary button--send"
:class="buttonClass" :class="buttonClass"
@ -91,10 +102,18 @@ export default {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
isOnPrivateNote: {
type: Boolean,
default: false,
},
enableRichEditor: { enableRichEditor: {
type: Boolean, type: Boolean,
default: false, default: false,
}, },
enterToSendEnabled: {
type: Boolean,
default: true,
},
}, },
computed: { computed: {
isNote() { isNote() {
@ -119,6 +138,9 @@ export default {
toggleFormatMode() { toggleFormatMode() {
this.setFormatMode(!this.isFormatMode); this.setFormatMode(!this.isFormatMode);
}, },
toggleEnterToSend() {
this.$emit('toggleEnterToSend', !this.enterToSendEnabled);
},
}, },
}; };
</script> </script>
@ -173,8 +195,8 @@ export default {
} }
.left-wrap { .left-wrap {
display: flex;
align-items: center; align-items: center;
display: flex;
} }
.button--reply { .button--reply {
@ -185,4 +207,21 @@ export default {
color: var(--s-600); color: var(--s-600);
font-size: var(--font-size-default); font-size: var(--font-size-default);
} }
.right-wrap {
display: flex;
.enter-to-send--checkbox {
align-items: center;
display: flex;
input {
margin: 0;
}
label {
color: var(--s-500);
}
}
}
</style> </style>

View File

@ -1,88 +1,45 @@
<template> <template>
<ul <mention-box :items="items" @mention-select="handleMentionClick" />
v-if="cannedMessages.length"
id="canned-list"
class="vertical dropdown menu canned"
:style="{ top: getTopPadding() + 'rem' }"
>
<li
v-for="(item, index) in cannedMessages"
:id="`canned-${index}`"
:key="item.short_code"
:class="{ active: index === selectedIndex }"
@click="onListItemSelection(index)"
@mouseover="onHover(index)"
>
<a class="text-truncate">
<strong>{{ item.short_code }}</strong> - {{ item.content }}
</a>
</li>
</ul>
</template> </template>
<script> <script>
import { mapGetters } from 'vuex'; import { mapGetters } from 'vuex';
import MentionBox from '../mentions/MentionBox.vue';
export default { export default {
props: ['onKeyenter', 'onClick'], components: { MentionBox },
data() { props: {
return { searchKey: {
selectedIndex: 0, type: String,
}; default: '',
},
}, },
computed: { computed: {
...mapGetters({ ...mapGetters({
cannedMessages: 'getCannedResponses', cannedMessages: 'getCannedResponses',
}), }),
items() {
return this.cannedMessages.map(cannedMessage => ({
label: cannedMessage.short_code,
key: cannedMessage.short_code,
description: cannedMessage.content,
}));
},
},
watch: {
searchKey() {
this.fetchCannedResponses();
},
}, },
mounted() { mounted() {
document.addEventListener('keydown', this.keyListener); this.fetchCannedResponses();
},
beforeDestroy() {
document.removeEventListener('keydown', this.keyListener);
}, },
methods: { methods: {
getTopPadding() { fetchCannedResponses() {
if (this.cannedMessages.length <= 4) { this.$store.dispatch('getCannedResponse', { searchKey: this.searchKey });
return -this.cannedMessages.length * 3.5;
}
return -14;
}, },
isUp(e) { handleMentionClick(item = {}) {
return e.keyCode === 38 || (e.ctrlKey && e.keyCode === 80); // UP, Ctrl-P this.$emit('click', item.description);
},
isDown(e) {
return e.keyCode === 40 || (e.ctrlKey && e.keyCode === 78); // DOWN, Ctrl-N
},
isEnter(e) {
return e.keyCode === 13;
},
keyListener(e) {
if (this.isUp(e)) {
if (!this.selectedIndex) {
this.selectedIndex = this.cannedMessages.length - 1;
} else {
this.selectedIndex -= 1;
}
}
if (this.isDown(e)) {
if (this.selectedIndex === this.cannedMessages.length - 1) {
this.selectedIndex = 0;
} else {
this.selectedIndex += 1;
}
}
if (this.isEnter(e)) {
this.onKeyenter(this.cannedMessages[this.selectedIndex].content);
}
this.$el.scrollTop = 34 * this.selectedIndex;
},
onHover(index) {
this.selectedIndex = index;
},
onListItemSelection(index) {
this.selectedIndex = index;
this.onClick(this.cannedMessages[this.selectedIndex].content);
}, },
}, },
}; };

View File

@ -35,6 +35,12 @@
{{ this.$t(`${attachmentIconKey}.CONTENT`) }} {{ this.$t(`${attachmentIconKey}.CONTENT`) }}
</span> </span>
</p> </p>
<p v-else class="conversation--message">
<i class="ion-android-alert"></i>
<span>
{{ this.$t(`CHAT_LIST.NO_MESSAGES`) }}
</span>
</p>
<div class="conversation--meta"> <div class="conversation--meta">
<span class="timestamp"> <span class="timestamp">
{{ dynamicTime(chat.timestamp) }} {{ dynamicTime(chat.timestamp) }}

View File

@ -21,8 +21,11 @@
:url="attachment.data_url" :url="attachment.data_url"
:readable-time="readableTime" :readable-time="readableTime"
/> />
<audio v-else-if="attachment.file_type === 'audio'" controls>
<source :src="attachment.data_url" />
</audio>
<bubble-file <bubble-file
v-if="attachment.file_type !== 'image'" v-else
:url="attachment.data_url" :url="attachment.data_url"
:readable-time="readableTime" :readable-time="readableTime"
/> />
@ -72,7 +75,7 @@ import Spinner from 'shared/components/Spinner';
import contentTypeMixin from 'shared/mixins/contentTypeMixin'; import contentTypeMixin from 'shared/mixins/contentTypeMixin';
import BubbleActions from './bubble/Actions'; import BubbleActions from './bubble/Actions';
import { MESSAGE_TYPE, MESSAGE_STATUS } from 'shared/constants/messages'; import { MESSAGE_TYPE, MESSAGE_STATUS } from 'shared/constants/messages';
import { generateBotMessageContent } from './helpers/botMessageContentHelper';
export default { export default {
components: { components: {
BubbleActions, BubbleActions,
@ -99,7 +102,19 @@ export default {
}, },
computed: { computed: {
message() { message() {
return this.formatMessage(this.data.content, this.isATweet); const botMessageContent = generateBotMessageContent(
this.contentType,
this.contentAttributes,
this.$t('CONVERSATION.NO_RESPONSE')
);
let messageContent =
this.formatMessage(this.data.content, this.isATweet) +
botMessageContent;
return messageContent;
},
contentAttributes() {
return this.data.content_attributes || {};
}, },
sender() { sender() {
return this.data.sender || {}; return this.data.sender || {};
@ -119,7 +134,7 @@ export default {
return !this.data.message_type ? 'left' : 'right'; return !this.data.message_type ? 'left' : 'right';
}, },
readableTime() { readableTime() {
return this.messageStamp(this.data.created_at); return this.messageStamp(this.data.created_at, 'LLL d, h:mm a');
}, },
isBubble() { isBubble() {
return [0, 1, 3].includes(this.data.message_type); return [0, 1, 3].includes(this.data.message_type);
@ -193,6 +208,22 @@ export default {
max-width: 32rem; max-width: 32rem;
padding: var(--space-small) var(--space-normal); padding: var(--space-small) var(--space-normal);
} }
&.is-private .file.message-text__wrap {
.ion-document-text {
color: var(--w-400);
}
.text-block-title {
color: #3c4858;
}
.download.button {
color: var(--w-400);
}
}
&.is-private.is-text > .message-text__wrap .link {
color: var(--w-700);
}
} }
&.is-pending { &.is-pending {

View File

@ -8,11 +8,10 @@
/> />
<div class="reply-box__top"> <div class="reply-box__top">
<canned-response <canned-response
v-if="showCannedResponsesList" v-if="showMentions && hasSlashCommand"
v-on-clickaway="hideCannedResponse" v-on-clickaway="hideMentions"
data-dropdown-menu :search-key="mentionSearchKey"
:on-keyenter="replaceText" @click="replaceText"
:on-click="replaceText"
/> />
<emoji-input <emoji-input
v-if="showEmojiPicker" v-if="showEmojiPicker"
@ -20,7 +19,7 @@
:on-click="emojiOnClick" :on-click="emojiOnClick"
/> />
<resizable-text-area <resizable-text-area
v-if="!isFormatMode" v-if="!showRichContentEditor"
ref="messageInput" ref="messageInput"
v-model="message" v-model="message"
class="input" class="input"
@ -35,12 +34,14 @@
v-else v-else
v-model="message" v-model="message"
class="input" class="input"
:is-private="isOnPrivateNote"
:placeholder="messagePlaceHolder" :placeholder="messagePlaceHolder"
:min-height="4" :min-height="4"
@typing-off="onTypingOff" @typing-off="onTypingOff"
@typing-on="onTypingOn" @typing-on="onTypingOn"
@focus="onFocus" @focus="onFocus"
@blur="onBlur" @blur="onBlur"
@toggle-user-mention="toggleUserMention"
/> />
</div> </div>
<div v-if="hasAttachments" class="attachment-preview-box"> <div v-if="hasAttachments" class="attachment-preview-box">
@ -59,8 +60,11 @@
:on-send="sendMessage" :on-send="sendMessage"
:is-send-disabled="isReplyButtonDisabled" :is-send-disabled="isReplyButtonDisabled"
:set-format-mode="setFormatMode" :set-format-mode="setFormatMode"
:is-format-mode="isFormatMode" :is-on-private-note="isOnPrivateNote"
:is-format-mode="showRichContentEditor"
:enable-rich-editor="isRichEditorEnabled" :enable-rich-editor="isRichEditorEnabled"
:enter-to-send-enabled="enterToSendEnabled"
@toggleEnterToSend="toggleEnterToSend"
/> />
</div> </div>
</template> </template>
@ -84,6 +88,7 @@ import {
} from 'shared/helpers/KeyboardHelpers'; } from 'shared/helpers/KeyboardHelpers';
import { MESSAGE_MAX_LENGTH } from 'shared/helpers/MessageTypeHelper'; import { MESSAGE_MAX_LENGTH } from 'shared/helpers/MessageTypeHelper';
import inboxMixin from 'shared/mixins/inboxMixin'; import inboxMixin from 'shared/mixins/inboxMixin';
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
export default { export default {
components: { components: {
@ -95,7 +100,7 @@ export default {
ReplyBottomPanel, ReplyBottomPanel,
WootMessageEditor, WootMessageEditor,
}, },
mixins: [clickaway, inboxMixin], mixins: [clickaway, inboxMixin, uiSettingsMixin],
props: { props: {
inReplyTo: { inReplyTo: {
type: [String, Number], type: [String, Number],
@ -107,18 +112,29 @@ export default {
message: '', message: '',
isFocused: false, isFocused: false,
showEmojiPicker: false, showEmojiPicker: false,
showCannedResponsesList: false, showMentions: false,
attachedFiles: [], attachedFiles: [],
isUploading: false, isUploading: false,
replyType: REPLY_EDITOR_MODES.REPLY, replyType: REPLY_EDITOR_MODES.REPLY,
isFormatMode: false, mentionSearchKey: '',
hasUserMention: false,
hasSlashCommand: false,
}; };
}, },
computed: { computed: {
showRichContentEditor() {
const {
display_rich_content_editor: displayRichContentEditor,
} = this.uiSettings;
return this.isOnPrivateNote || displayRichContentEditor;
},
...mapGetters({ currentChat: 'getSelectedChat' }), ...mapGetters({ currentChat: 'getSelectedChat' }),
enterToSendEnabled() {
return !!this.uiSettings.enter_to_send_enabled;
},
isPrivate() { isPrivate() {
if (this.currentChat.can_reply) { if (this.currentChat.can_reply) {
return this.replyType === REPLY_EDITOR_MODES.NOTE; return this.isOnPrivateNote;
} }
return true; return true;
}, },
@ -200,15 +216,20 @@ export default {
}, },
isRichEditorEnabled() { isRichEditorEnabled() {
return ( return (
this.isAWebWidgetInbox || this.isAWebWidgetInbox || this.isAnEmailChannel || this.isOnPrivateNote
this.isAnEmailChannel ||
this.replyType === REPLY_EDITOR_MODES.NOTE
); );
}, },
isOnPrivateNote() {
return this.replyType === REPLY_EDITOR_MODES.NOTE;
},
}, },
watch: { watch: {
currentChat(conversation) { currentChat(conversation) {
const { can_reply: canReply } = conversation; const { can_reply: canReply } = conversation;
if (this.isOnPrivateNote) {
return;
}
if (canReply) { if (canReply) {
this.replyType = REPLY_EDITOR_MODES.REPLY; this.replyType = REPLY_EDITOR_MODES.REPLY;
} else { } else {
@ -216,22 +237,15 @@ export default {
} }
}, },
message(updatedMessage) { message(updatedMessage) {
if (this.isPrivate) { this.hasSlashCommand = updatedMessage[0] === '/';
return;
}
const isSlashCommand = updatedMessage[0] === '/';
const hasNextWord = updatedMessage.includes(' '); const hasNextWord = updatedMessage.includes(' ');
const isShortCodeActive = isSlashCommand && !hasNextWord; const isShortCodeActive = this.hasSlashCommand && !hasNextWord;
if (isShortCodeActive) { if (isShortCodeActive) {
this.showCannedResponsesList = true; this.mentionSearchKey = updatedMessage.substr(1, updatedMessage.length);
if (updatedMessage.length > 1) { this.showMentions = true;
const searchKey = updatedMessage.substr(1, updatedMessage.length);
this.$store.dispatch('getCannedResponse', { searchKey });
} else {
this.$store.dispatch('getCannedResponse');
}
} else { } else {
this.showCannedResponsesList = false; this.mentionSearchKey = '';
this.showMentions = false;
} }
}, },
}, },
@ -242,24 +256,35 @@ export default {
document.removeEventListener('keydown', this.handleKeyEvents); document.removeEventListener('keydown', this.handleKeyEvents);
}, },
methods: { methods: {
toggleUserMention(currentMentionState) {
this.hasUserMention = currentMentionState;
},
handleKeyEvents(e) { handleKeyEvents(e) {
if (isEscape(e)) { if (isEscape(e)) {
this.hideEmojiPicker(); this.hideEmojiPicker();
this.hideCannedResponse(); this.hideMentions();
} else if (isEnter(e)) { } else if (isEnter(e)) {
const hasSendOnEnterEnabled =
(this.showRichContentEditor &&
this.enterToSendEnabled &&
!this.hasUserMention) ||
!this.showRichContentEditor;
const shouldSendMessage = const shouldSendMessage =
!this.isFormatMode && !hasPressedShift(e) && this.isFocused; hasSendOnEnterEnabled && !hasPressedShift(e) && this.isFocused;
if (shouldSendMessage) { if (shouldSendMessage) {
e.preventDefault(); e.preventDefault();
this.sendMessage(); this.sendMessage();
} }
} }
}, },
toggleEnterToSend(enterToSendEnabled) {
this.updateUISettings({ enter_to_send_enabled: enterToSendEnabled });
},
async sendMessage() { async sendMessage() {
if (this.isReplyButtonDisabled) { if (this.isReplyButtonDisabled) {
return; return;
} }
if (!this.showCannedResponsesList) { if (!this.showMentions) {
const newMessage = this.message; const newMessage = this.message;
const messagePayload = this.getMessagePayload(newMessage); const messagePayload = this.getMessagePayload(newMessage);
this.clearMessage(); this.clearMessage();
@ -281,7 +306,10 @@ export default {
const { can_reply: canReply } = this.currentChat; const { can_reply: canReply } = this.currentChat;
if (canReply) this.replyType = mode; if (canReply) this.replyType = mode;
this.$refs.messageInput.focus(); if (this.showRichContentEditor) {
return;
}
this.$nextTick(() => this.$refs.messageInput.focus());
}, },
emojiOnClick(emoji) { emojiOnClick(emoji) {
this.message = `${this.message}${emoji} `; this.message = `${this.message}${emoji} `;
@ -298,8 +326,8 @@ export default {
this.toggleEmojiPicker(); this.toggleEmojiPicker();
} }
}, },
hideCannedResponse() { hideMentions() {
this.showCannedResponsesList = false; this.showMentions = false;
}, },
onTypingOn() { onTypingOn() {
this.toggleTyping('on'); this.toggleTyping('on');
@ -363,7 +391,7 @@ export default {
return messagePayload; return messagePayload;
}, },
setFormatMode(value) { setFormatMode(value) {
this.isFormatMode = value; this.updateUISettings({ display_rich_content_editor: value });
}, },
}, },
}; };

View File

@ -0,0 +1,49 @@
<template>
<mention-box :items="items" @mention-select="handleMentionClick" />
</template>
<script>
import { mapGetters } from 'vuex';
import MentionBox from '../mentions/MentionBox.vue';
export default {
components: { MentionBox },
props: {
searchKey: {
type: String,
default: '',
},
},
computed: {
...mapGetters({
agents: 'agents/getVerifiedAgents',
}),
items() {
if (!this.searchKey) {
return this.agents.map(agent => ({
label: agent.name,
key: agent.id,
description: agent.email,
}));
}
return this.agents
.filter(agent =>
agent.name
.toLocaleLowerCase()
.includes(this.searchKey.toLocaleLowerCase())
)
.map(agent => ({
label: agent.name,
key: agent.id,
description: agent.email,
}));
},
},
methods: {
handleMentionClick(item = {}) {
this.$emit('click', item);
},
},
};
</script>

View File

@ -0,0 +1,57 @@
const generateInputSelectContent = (contentType, contentAttributes) => {
const { submitted_values: submittedValues = [] } = contentAttributes;
const [selectedOption] = submittedValues;
if (selectedOption && selectedOption.title) {
return `<strong>${selectedOption.title}</strong>`;
}
return '';
};
const generateInputEmailContent = (contentType, contentAttributes) => {
const { submitted_email: submittedEmail = '' } = contentAttributes;
if (submittedEmail) {
return `<strong>${submittedEmail}</strong>`;
}
return '';
};
const generateFormContent = (
contentType,
contentAttributes,
noResponseText
) => {
const { items, submitted_values: submittedValues = [] } = contentAttributes;
if (submittedValues.length) {
const submittedObject = submittedValues.reduce((acc, keyValuePair) => {
acc[keyValuePair.name] = keyValuePair.value;
return acc;
}, {});
let formMessageContent = '';
items.forEach(item => {
formMessageContent += `<div>${item.label}</div>`;
const response = submittedObject[item.name] || noResponseText;
formMessageContent += `<strong>${response}</strong><br/><br/>`;
});
return formMessageContent;
}
return '';
};
export const generateBotMessageContent = (
contentType,
contentAttributes,
noResponseText = 'No response'
) => {
const contentTypeMethods = {
input_select: generateInputSelectContent,
input_email: generateInputEmailContent,
form: generateFormContent,
};
const contentTypeMethod = contentTypeMethods[contentType];
if (contentTypeMethod && typeof contentTypeMethod === 'function') {
return contentTypeMethod(contentType, contentAttributes, noResponseText);
}
return '';
};

View File

@ -0,0 +1,39 @@
import { generateBotMessageContent } from '../botMessageContentHelper';
describe('#generateBotMessageContent', () => {
it('return correct input_select content', () => {
expect(
generateBotMessageContent('input_select', {
submitted_values: [{ value: 'salad', title: 'Salad' }],
})
).toEqual('<strong>Salad</strong>');
});
it('return correct input_email content', () => {
expect(
generateBotMessageContent('input_email', {
submitted_email: 'hello@chatwoot.com',
})
).toEqual('<strong>hello@chatwoot.com</strong>');
});
it('return correct form content', () => {
expect(
generateBotMessageContent('form', {
items: [
{
name: 'large_text',
label: 'This a large text',
},
{
name: 'email',
label: 'Email',
},
],
submitted_values: [{ name: 'large_text', value: 'Large Text Content' }],
})
).toEqual(
'<div>This a large text</div><strong>Large Text Content</strong><br/><br/><div>Email</div><strong>No response</strong><br/><br/>'
);
});
});

View File

@ -0,0 +1,115 @@
<template>
<ul
v-if="items.length"
class="vertical dropdown menu mention--box"
:style="{ top: getTopPadding() + 'rem' }"
>
<li
v-for="(item, index) in items"
:id="`mention-item-${index}`"
:key="item.key"
:class="{ active: index === selectedIndex }"
@click="onListItemSelection(index)"
@mouseover="onHover(index)"
>
<a class="text-truncate">
<strong>{{ item.label }}</strong> - {{ item.description }}
</a>
</li>
</ul>
</template>
<script>
export default {
props: {
items: {
type: Array,
default: () => {},
},
},
data() {
return {
selectedIndex: 0,
};
},
watch: {
items(newItems) {
if (newItems.length < this.selectedIndex + 1) {
this.selectedIndex = 0;
}
},
},
mounted() {
document.addEventListener('keydown', this.keyListener);
},
beforeDestroy() {
document.removeEventListener('keydown', this.keyListener);
},
methods: {
getTopPadding() {
if (this.items.length <= 4) {
return -(this.items.length * 2.8 + 1.7);
}
return -14;
},
isUp(e) {
return e.keyCode === 38 || (e.ctrlKey && e.keyCode === 80); // UP, Ctrl-P
},
isDown(e) {
return e.keyCode === 40 || (e.ctrlKey && e.keyCode === 78); // DOWN, Ctrl-N
},
isEnter(e) {
return e.keyCode === 13;
},
keyListener(e) {
if (this.isUp(e)) {
if (!this.selectedIndex) {
this.selectedIndex = this.items.length - 1;
} else {
this.selectedIndex -= 1;
}
}
if (this.isDown(e)) {
if (this.selectedIndex === this.items.length - 1) {
this.selectedIndex = 0;
} else {
this.selectedIndex += 1;
}
}
if (this.isEnter(e)) {
this.onMentionSelect();
}
this.$el.scrollTop = 28 * this.selectedIndex;
},
onHover(index) {
this.selectedIndex = index;
},
onListItemSelection(index) {
this.selectedIndex = index;
this.onMentionSelect();
},
onMentionSelect() {
this.$emit('mention-select', this.items[this.selectedIndex]);
},
},
};
</script>
<style scoped lang="scss">
.mention--box {
background: var(--white);
border-bottom: var(--space-small) solid var(--white);
border-top: 1px solid var(--color-border);
left: 0;
max-height: 14rem;
overflow: auto;
padding-top: var(--space-small);
position: absolute;
width: 100%;
z-index: 100;
.active a {
background: var(--w-500);
}
}
</style>

View File

@ -8,11 +8,15 @@ export const getSidebarItems = accountId => ({
'inbox_conversation', 'inbox_conversation',
'conversation_through_inbox', 'conversation_through_inbox',
'contacts_dashboard', 'contacts_dashboard',
'notifications_dashboard',
'settings_account_reports', 'settings_account_reports',
'profile_settings', 'profile_settings',
'profile_settings_index', 'profile_settings_index',
'label_conversations', 'label_conversations',
'conversations_through_label', 'conversations_through_label',
'team_conversations',
'conversations_through_team',
'notifications_index',
], ],
menuItems: { menuItems: {
assignedToMe: { assignedToMe: {
@ -31,6 +35,13 @@ export const getSidebarItems = accountId => ({
toState: frontendURL(`accounts/${accountId}/contacts`), toState: frontendURL(`accounts/${accountId}/contacts`),
toStateName: 'contacts_dashboard', toStateName: 'contacts_dashboard',
}, },
notifications: {
icon: 'ion-ios-bell',
label: 'NOTIFICATIONS',
hasSubMenu: false,
toState: frontendURL(`accounts/${accountId}/notifications`),
toStateName: 'notifications_dashboard',
},
report: { report: {
icon: 'ion-arrow-graph-up-right', icon: 'ion-arrow-graph-up-right',
label: 'REPORTS', label: 'REPORTS',

View File

@ -79,6 +79,7 @@
}, },
"RECEIVED_VIA_EMAIL": "تم تلقيه عبر البريد الإلكتروني", "RECEIVED_VIA_EMAIL": "تم تلقيه عبر البريد الإلكتروني",
"VIEW_TWEET_IN_TWITTER": "عرض التغريدة في تويتر", "VIEW_TWEET_IN_TWITTER": "عرض التغريدة في تويتر",
"REPLY_TO_TWEET": "الرد على هذه التغريدة" "REPLY_TO_TWEET": "الرد على هذه التغريدة",
"NO_MESSAGES": "No Messages"
} }
} }

View File

@ -25,6 +25,7 @@
"REMOVE_SELECTION": "إزالة التحديد", "REMOVE_SELECTION": "إزالة التحديد",
"DOWNLOAD": "تنزيل", "DOWNLOAD": "تنزيل",
"UPLOADING_ATTACHMENTS": "Uploading attachments...", "UPLOADING_ATTACHMENTS": "Uploading attachments...",
"NO_RESPONSE": "No response",
"HEADER": { "HEADER": {
"RESOLVE_ACTION": "إغلاق المحادثة", "RESOLVE_ACTION": "إغلاق المحادثة",
"REOPEN_ACTION": "إعادة فتح", "REOPEN_ACTION": "إعادة فتح",
@ -44,7 +45,8 @@
"TWEET": "تغريد", "TWEET": "تغريد",
"TIP_FORMAT_ICON": "Show rich text editor", "TIP_FORMAT_ICON": "Show rich text editor",
"TIP_EMOJI_ICON": "Show emoji selector", "TIP_EMOJI_ICON": "Show emoji selector",
"TIP_ATTACH_ICON": "Attach files" "TIP_ATTACH_ICON": "Attach files",
"ENTER_TO_SEND": "Enter to send"
}, },
"VISIBLE_TO_AGENTS": "ملاحظة خاصة: مرئية فقط لأعضاء فريق العمل والموظفين", "VISIBLE_TO_AGENTS": "ملاحظة خاصة: مرئية فقط لأعضاء فريق العمل والموظفين",
"CHANGE_STATUS": "تم تغيير حالة المحادثة", "CHANGE_STATUS": "تم تغيير حالة المحادثة",

View File

@ -51,5 +51,25 @@
"ENTER_TO_REMOVE": "Press enter to remove", "ENTER_TO_REMOVE": "Press enter to remove",
"SELECT_ONE": "Select one" "SELECT_ONE": "Select one"
} }
},
"NOTIFICATIONS_PAGE": {
"HEADER": "الإشعارات",
"MARK_ALL_DONE": "Mark All Done",
"LIST": {
"LOADING_MESSAGE": "Loading notifications...",
"404": "No Notifications",
"TABLE_HEADER": [
"الاسم",
"رقم الهاتف",
"المحادثات",
"Last Contacted"
]
},
"TYPE_LABEL": {
"conversation_creation": "New conversation",
"conversation_assignment": "Conversation Assigned",
"assigned_conversation_new_message": "New Message",
"conversation_mention": "Mention"
}
} }
} }

View File

@ -27,6 +27,7 @@
"NOTE": "قم بتحديث تفضيلات إشعار البريد الإلكتروني الخاص بك من هنا", "NOTE": "قم بتحديث تفضيلات إشعار البريد الإلكتروني الخاص بك من هنا",
"CONVERSATION_ASSIGNMENT": "إرسال إشعارات البريد الإلكتروني عند إسناد محادثة لي", "CONVERSATION_ASSIGNMENT": "إرسال إشعارات البريد الإلكتروني عند إسناد محادثة لي",
"CONVERSATION_CREATION": "إرسال إشعارات للبريد الإلكتروني عند ورود محادثة جديدة", "CONVERSATION_CREATION": "إرسال إشعارات للبريد الإلكتروني عند ورود محادثة جديدة",
"CONVERSATION_MENTION": "Send email notifications when you are mentioned in a conversation",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send email notifications when a new message is created in an assigned conversation" "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send email notifications when a new message is created in an assigned conversation"
}, },
"API": { "API": {
@ -38,6 +39,7 @@
"NOTE": "قم بتحديث تفضيلات إشعارات المتصفح من هنا", "NOTE": "قم بتحديث تفضيلات إشعارات المتصفح من هنا",
"CONVERSATION_ASSIGNMENT": "إرسال إشعارات على المتصفح عند إسناد محادثة لي", "CONVERSATION_ASSIGNMENT": "إرسال إشعارات على المتصفح عند إسناد محادثة لي",
"CONVERSATION_CREATION": "إرسال إشعارات المتصفح عند ورود محادثة جديدة", "CONVERSATION_CREATION": "إرسال إشعارات المتصفح عند ورود محادثة جديدة",
"CONVERSATION_MENTION": "Send push notifications when you are mentioned in a conversation",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send push notifications when a new message is created in an assigned conversation", "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send push notifications when a new message is created in an assigned conversation",
"HAS_ENABLED_PUSH": "لقد قمت بتمكين الإشعارات لهذا المتصفح.", "HAS_ENABLED_PUSH": "لقد قمت بتمكين الإشعارات لهذا المتصفح.",
"REQUEST_PUSH": "تفعيل إشعارات المتصفح" "REQUEST_PUSH": "تفعيل إشعارات المتصفح"
@ -118,10 +120,12 @@
"HOME": "الرئيسية", "HOME": "الرئيسية",
"AGENTS": "موظف الدعم", "AGENTS": "موظف الدعم",
"INBOXES": "قنوات التواصل", "INBOXES": "قنوات التواصل",
"NOTIFICATIONS": "الإشعارات",
"CANNED_RESPONSES": "الردود السريعة", "CANNED_RESPONSES": "الردود السريعة",
"INTEGRATIONS": "خيارات الربط", "INTEGRATIONS": "خيارات الربط",
"ACCOUNT_SETTINGS": "إعدادات الحساب", "ACCOUNT_SETTINGS": "إعدادات الحساب",
"LABELS": "الوسوم" "LABELS": "الوسوم",
"TEAMS": "Teams"
}, },
"CREATE_ACCOUNT": { "CREATE_ACCOUNT": {
"NEW_ACCOUNT": "حساب جديد", "NEW_ACCOUNT": "حساب جديد",

View File

@ -79,6 +79,7 @@
}, },
"RECEIVED_VIA_EMAIL": "Rebut per correu electrònic", "RECEIVED_VIA_EMAIL": "Rebut per correu electrònic",
"VIEW_TWEET_IN_TWITTER": "Veure el tuit a Twitter", "VIEW_TWEET_IN_TWITTER": "Veure el tuit a Twitter",
"REPLY_TO_TWEET": "Respon a aquest tuit" "REPLY_TO_TWEET": "Respon a aquest tuit",
"NO_MESSAGES": "Cap Missatge"
} }
} }

View File

@ -25,6 +25,7 @@
"REMOVE_SELECTION": "Elimina la selecció", "REMOVE_SELECTION": "Elimina la selecció",
"DOWNLOAD": "Descarrega", "DOWNLOAD": "Descarrega",
"UPLOADING_ATTACHMENTS": "Pujant fitxers adjunts...", "UPLOADING_ATTACHMENTS": "Pujant fitxers adjunts...",
"NO_RESPONSE": "Sense resposta",
"HEADER": { "HEADER": {
"RESOLVE_ACTION": "Resoldre", "RESOLVE_ACTION": "Resoldre",
"REOPEN_ACTION": "Tornar a obrir", "REOPEN_ACTION": "Tornar a obrir",
@ -42,9 +43,10 @@
"SEND": "Envia", "SEND": "Envia",
"CREATE": "Afegeix una nota", "CREATE": "Afegeix una nota",
"TWEET": "Tuit", "TWEET": "Tuit",
"TIP_FORMAT_ICON": "Show rich text editor", "TIP_FORMAT_ICON": "Mostra l'editor de text enriquit",
"TIP_EMOJI_ICON": "Show emoji selector", "TIP_EMOJI_ICON": "Mostra la selecció d'emoticones",
"TIP_ATTACH_ICON": "Attach files" "TIP_ATTACH_ICON": "Ajuntar fitxers",
"ENTER_TO_SEND": "Intro per enviar"
}, },
"VISIBLE_TO_AGENTS": "Nota privada: Només és visible per tu i el vostre equip", "VISIBLE_TO_AGENTS": "Nota privada: Només és visible per tu i el vostre equip",
"CHANGE_STATUS": "Estat de la conversa canviat", "CHANGE_STATUS": "Estat de la conversa canviat",

View File

@ -43,7 +43,7 @@
"CUSTOM_EMAIL_DOMAIN_ENABLED": "Ara podeu rebre correus electrònics al vostre domini personalitzat." "CUSTOM_EMAIL_DOMAIN_ENABLED": "Ara podeu rebre correus electrònics al vostre domini personalitzat."
} }
}, },
"UPDATE_CHATWOOT": "An update %{latestChatwootVersion} for Chatwoot is available. Please update your instance." "UPDATE_CHATWOOT": "L'actualització %{latestChatwootVersion} per Chatwoot està disponible. Si us plau, actualitza l'instancia."
}, },
"FORMS": { "FORMS": {
"MULTISELECT": { "MULTISELECT": {
@ -51,5 +51,25 @@
"ENTER_TO_REMOVE": "Presiona retorn (tecla enter) per eliminar", "ENTER_TO_REMOVE": "Presiona retorn (tecla enter) per eliminar",
"SELECT_ONE": "Selecciona un" "SELECT_ONE": "Selecciona un"
} }
},
"NOTIFICATIONS_PAGE": {
"HEADER": "Notificacions",
"MARK_ALL_DONE": "Marca Tot Fet",
"LIST": {
"LOADING_MESSAGE": "Carregant notificacions...",
"404": "Cap Notificació",
"TABLE_HEADER": [
"Nom",
"Número de telèfon",
"Converses",
"Darrer contacte"
]
},
"TYPE_LABEL": {
"conversation_creation": "Nova conversació",
"conversation_assignment": "Conversació Assignada",
"assigned_conversation_new_message": "Missatge Nou",
"conversation_mention": "Menció"
}
} }
} }

View File

@ -242,7 +242,7 @@
"INBOX_UPDATE_TITLE": "Configuració de la safata d'entrada", "INBOX_UPDATE_TITLE": "Configuració de la safata d'entrada",
"INBOX_UPDATE_SUB_TEXT": "Actualitza la configuració de la safata d'entrada", "INBOX_UPDATE_SUB_TEXT": "Actualitza la configuració de la safata d'entrada",
"AUTO_ASSIGNMENT_SUB_TEXT": "Activa o desactiva l'assignació automàtica d'agents disponibles a les noves converses", "AUTO_ASSIGNMENT_SUB_TEXT": "Activa o desactiva l'assignació automàtica d'agents disponibles a les noves converses",
"HMAC_VERIFICATION": "User Identity Validation", "HMAC_VERIFICATION": "Validació de la Identitat del Usuari",
"HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." "HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here."
}, },
"FACEBOOK_REAUTHORIZE": { "FACEBOOK_REAUTHORIZE": {

View File

@ -27,6 +27,7 @@
"NOTE": "Actualitza aqui les preferències de les notificacions per correu electrònic", "NOTE": "Actualitza aqui les preferències de les notificacions per correu electrònic",
"CONVERSATION_ASSIGNMENT": "Envieu notificacions per correu electrònic quan se massigni una conversa", "CONVERSATION_ASSIGNMENT": "Envieu notificacions per correu electrònic quan se massigni una conversa",
"CONVERSATION_CREATION": "Envieu notificacions per correu electrònic quan es crea una nova conversa", "CONVERSATION_CREATION": "Envieu notificacions per correu electrònic quan es crea una nova conversa",
"CONVERSATION_MENTION": "Enviar notificacions per mail quan siguis esmentat en una conversació",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Envia notificacions per correu electrònic quan es creï un missatge nou en una conversa assignada" "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Envia notificacions per correu electrònic quan es creï un missatge nou en una conversa assignada"
}, },
"API": { "API": {
@ -38,6 +39,7 @@
"NOTE": "Actualitzeu les preferències de notificació aquí", "NOTE": "Actualitzeu les preferències de notificació aquí",
"CONVERSATION_ASSIGNMENT": "Envia notificacions automàtiques quan se m'assigna una conversa", "CONVERSATION_ASSIGNMENT": "Envia notificacions automàtiques quan se m'assigna una conversa",
"CONVERSATION_CREATION": "Envia notificacions automàtiques quan es creï una conversa nova", "CONVERSATION_CREATION": "Envia notificacions automàtiques quan es creï una conversa nova",
"CONVERSATION_MENTION": "Enviar notificacions push quan siguis esmentat en una conversació",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Envia notificacions automàtiques quan es creï un missatge nou en una conversa assignada", "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Envia notificacions automàtiques quan es creï un missatge nou en una conversa assignada",
"HAS_ENABLED_PUSH": "Heu activat les notificacions per a aquest navegador.", "HAS_ENABLED_PUSH": "Heu activat les notificacions per a aquest navegador.",
"REQUEST_PUSH": "Activa les notificacions" "REQUEST_PUSH": "Activa les notificacions"
@ -118,10 +120,12 @@
"HOME": "Inici", "HOME": "Inici",
"AGENTS": "Agents", "AGENTS": "Agents",
"INBOXES": "Safates d'entrada", "INBOXES": "Safates d'entrada",
"NOTIFICATIONS": "Notificacions",
"CANNED_RESPONSES": "Respostes predeterminades", "CANNED_RESPONSES": "Respostes predeterminades",
"INTEGRATIONS": "Integracions", "INTEGRATIONS": "Integracions",
"ACCOUNT_SETTINGS": "Configuració del compte", "ACCOUNT_SETTINGS": "Configuració del compte",
"LABELS": "Etiquetes" "LABELS": "Etiquetes",
"TEAMS": "Teams"
}, },
"CREATE_ACCOUNT": { "CREATE_ACCOUNT": {
"NEW_ACCOUNT": "Compte nou", "NEW_ACCOUNT": "Compte nou",

View File

@ -79,6 +79,7 @@
}, },
"RECEIVED_VIA_EMAIL": "Obdrženo e-mailem", "RECEIVED_VIA_EMAIL": "Obdrženo e-mailem",
"VIEW_TWEET_IN_TWITTER": "Zobrazit tweet na Twitteru", "VIEW_TWEET_IN_TWITTER": "Zobrazit tweet na Twitteru",
"REPLY_TO_TWEET": "Odpovědět na tento tweet" "REPLY_TO_TWEET": "Odpovědět na tento tweet",
"NO_MESSAGES": "No Messages"
} }
} }

View File

@ -25,6 +25,7 @@
"REMOVE_SELECTION": "Odstranit výběr", "REMOVE_SELECTION": "Odstranit výběr",
"DOWNLOAD": "Stáhnout", "DOWNLOAD": "Stáhnout",
"UPLOADING_ATTACHMENTS": "Nahrávání příloh...", "UPLOADING_ATTACHMENTS": "Nahrávání příloh...",
"NO_RESPONSE": "No response",
"HEADER": { "HEADER": {
"RESOLVE_ACTION": "Vyřešit", "RESOLVE_ACTION": "Vyřešit",
"REOPEN_ACTION": "Znovu otevřít", "REOPEN_ACTION": "Znovu otevřít",
@ -44,7 +45,8 @@
"TWEET": "Tweet", "TWEET": "Tweet",
"TIP_FORMAT_ICON": "Show rich text editor", "TIP_FORMAT_ICON": "Show rich text editor",
"TIP_EMOJI_ICON": "Show emoji selector", "TIP_EMOJI_ICON": "Show emoji selector",
"TIP_ATTACH_ICON": "Attach files" "TIP_ATTACH_ICON": "Attach files",
"ENTER_TO_SEND": "Enter to send"
}, },
"VISIBLE_TO_AGENTS": "Soukromá poznámka: Viditelné pouze pro vás a váš tým", "VISIBLE_TO_AGENTS": "Soukromá poznámka: Viditelné pouze pro vás a váš tým",
"CHANGE_STATUS": "Stav konverzace byl změněn", "CHANGE_STATUS": "Stav konverzace byl změněn",

View File

@ -51,5 +51,25 @@
"ENTER_TO_REMOVE": "Stiskněte Enter pro odebrání", "ENTER_TO_REMOVE": "Stiskněte Enter pro odebrání",
"SELECT_ONE": "Vyberte jeden" "SELECT_ONE": "Vyberte jeden"
} }
},
"NOTIFICATIONS_PAGE": {
"HEADER": "Notifications",
"MARK_ALL_DONE": "Mark All Done",
"LIST": {
"LOADING_MESSAGE": "Loading notifications...",
"404": "No Notifications",
"TABLE_HEADER": [
"Název",
"Telefonní číslo",
"Konverzace",
"Naposledy kontaktováno"
]
},
"TYPE_LABEL": {
"conversation_creation": "New conversation",
"conversation_assignment": "Conversation Assigned",
"assigned_conversation_new_message": "New Message",
"conversation_mention": "Mention"
}
} }
} }

View File

@ -27,6 +27,7 @@
"NOTE": "Zde aktualizujte nastavení e-mailových oznámení", "NOTE": "Zde aktualizujte nastavení e-mailových oznámení",
"CONVERSATION_ASSIGNMENT": "Odeslat e-mailová oznámení, když je mi přiřazena konverzace", "CONVERSATION_ASSIGNMENT": "Odeslat e-mailová oznámení, když je mi přiřazena konverzace",
"CONVERSATION_CREATION": "Odeslat oznámení e-mailem při vytváření nové konverzace", "CONVERSATION_CREATION": "Odeslat oznámení e-mailem při vytváření nové konverzace",
"CONVERSATION_MENTION": "Send email notifications when you are mentioned in a conversation",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send email notifications when a new message is created in an assigned conversation" "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send email notifications when a new message is created in an assigned conversation"
}, },
"API": { "API": {
@ -38,6 +39,7 @@
"NOTE": "Update your push notification preferences here", "NOTE": "Update your push notification preferences here",
"CONVERSATION_ASSIGNMENT": "Send push notifications when a conversation is assigned to me", "CONVERSATION_ASSIGNMENT": "Send push notifications when a conversation is assigned to me",
"CONVERSATION_CREATION": "Send push notifications when a new conversation is created", "CONVERSATION_CREATION": "Send push notifications when a new conversation is created",
"CONVERSATION_MENTION": "Send push notifications when you are mentioned in a conversation",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send push notifications when a new message is created in an assigned conversation", "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send push notifications when a new message is created in an assigned conversation",
"HAS_ENABLED_PUSH": "You have enabled push for this browser.", "HAS_ENABLED_PUSH": "You have enabled push for this browser.",
"REQUEST_PUSH": "Enable push notifications" "REQUEST_PUSH": "Enable push notifications"
@ -98,7 +100,7 @@
}, },
"FILE_BUBBLE": { "FILE_BUBBLE": {
"DOWNLOAD": "Stáhnout", "DOWNLOAD": "Stáhnout",
"UPLOADING": "Uploading..." "UPLOADING": "Nahrávání..."
}, },
"FORM_BUBBLE": { "FORM_BUBBLE": {
"SUBMIT": "Odeslat" "SUBMIT": "Odeslat"
@ -118,10 +120,12 @@
"HOME": "Home", "HOME": "Home",
"AGENTS": "Agenti", "AGENTS": "Agenti",
"INBOXES": "Krabice", "INBOXES": "Krabice",
"NOTIFICATIONS": "Notifications",
"CANNED_RESPONSES": "Konzervované odpovědi", "CANNED_RESPONSES": "Konzervované odpovědi",
"INTEGRATIONS": "Integrace", "INTEGRATIONS": "Integrace",
"ACCOUNT_SETTINGS": "Account Settings", "ACCOUNT_SETTINGS": "Account Settings",
"LABELS": "Labels" "LABELS": "Labels",
"TEAMS": "Teams"
}, },
"CREATE_ACCOUNT": { "CREATE_ACCOUNT": {
"NEW_ACCOUNT": "New Account", "NEW_ACCOUNT": "New Account",

View File

@ -79,6 +79,7 @@
}, },
"RECEIVED_VIA_EMAIL": "Modtaget via e-mail", "RECEIVED_VIA_EMAIL": "Modtaget via e-mail",
"VIEW_TWEET_IN_TWITTER": "Se tweet på Twitter", "VIEW_TWEET_IN_TWITTER": "Se tweet på Twitter",
"REPLY_TO_TWEET": "Svar på dette tweet" "REPLY_TO_TWEET": "Svar på dette tweet",
"NO_MESSAGES": "No Messages"
} }
} }

View File

@ -25,6 +25,7 @@
"REMOVE_SELECTION": "Fjern Markering", "REMOVE_SELECTION": "Fjern Markering",
"DOWNLOAD": "Download", "DOWNLOAD": "Download",
"UPLOADING_ATTACHMENTS": "Uploading attachments...", "UPLOADING_ATTACHMENTS": "Uploading attachments...",
"NO_RESPONSE": "No response",
"HEADER": { "HEADER": {
"RESOLVE_ACTION": "Løs", "RESOLVE_ACTION": "Løs",
"REOPEN_ACTION": "Genåben", "REOPEN_ACTION": "Genåben",
@ -44,7 +45,8 @@
"TWEET": "Tweet", "TWEET": "Tweet",
"TIP_FORMAT_ICON": "Show rich text editor", "TIP_FORMAT_ICON": "Show rich text editor",
"TIP_EMOJI_ICON": "Show emoji selector", "TIP_EMOJI_ICON": "Show emoji selector",
"TIP_ATTACH_ICON": "Attach files" "TIP_ATTACH_ICON": "Attach files",
"ENTER_TO_SEND": "Enter to send"
}, },
"VISIBLE_TO_AGENTS": "Privat Note: Kun synlig for dig og dit team", "VISIBLE_TO_AGENTS": "Privat Note: Kun synlig for dig og dit team",
"CHANGE_STATUS": "Samtalestatus ændret", "CHANGE_STATUS": "Samtalestatus ændret",

View File

@ -51,5 +51,25 @@
"ENTER_TO_REMOVE": "Press enter to remove", "ENTER_TO_REMOVE": "Press enter to remove",
"SELECT_ONE": "Select one" "SELECT_ONE": "Select one"
} }
},
"NOTIFICATIONS_PAGE": {
"HEADER": "Notifications",
"MARK_ALL_DONE": "Mark All Done",
"LIST": {
"LOADING_MESSAGE": "Loading notifications...",
"404": "No Notifications",
"TABLE_HEADER": [
"Navn",
"Telefonnummer",
"Samtaler",
"Sidst Kontaktet"
]
},
"TYPE_LABEL": {
"conversation_creation": "New conversation",
"conversation_assignment": "Conversation Assigned",
"assigned_conversation_new_message": "New Message",
"conversation_mention": "Mention"
}
} }
} }

View File

@ -27,6 +27,7 @@
"NOTE": "Opdater dine e-mail notifikationspræferencer her", "NOTE": "Opdater dine e-mail notifikationspræferencer her",
"CONVERSATION_ASSIGNMENT": "Send e-mail notifikationer når en samtale er tildelt mig", "CONVERSATION_ASSIGNMENT": "Send e-mail notifikationer når en samtale er tildelt mig",
"CONVERSATION_CREATION": "Send e-mail notifikationer når en ny samtale er oprettet", "CONVERSATION_CREATION": "Send e-mail notifikationer når en ny samtale er oprettet",
"CONVERSATION_MENTION": "Send email notifications when you are mentioned in a conversation",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send e-mail notifikationer når en ny besked er oprettet i en tildelt samtale" "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send e-mail notifikationer når en ny besked er oprettet i en tildelt samtale"
}, },
"API": { "API": {
@ -38,6 +39,7 @@
"NOTE": "Opdater dine push-notifikationspræferencer her", "NOTE": "Opdater dine push-notifikationspræferencer her",
"CONVERSATION_ASSIGNMENT": "Send push-notifikationer, når en samtale er tildelt mig", "CONVERSATION_ASSIGNMENT": "Send push-notifikationer, når en samtale er tildelt mig",
"CONVERSATION_CREATION": "Send push-notifikationer, når en ny samtale er oprettet", "CONVERSATION_CREATION": "Send push-notifikationer, når en ny samtale er oprettet",
"CONVERSATION_MENTION": "Send push notifications when you are mentioned in a conversation",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send push-notifikationer, når en ny besked oprettes i en tildelt samtale", "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send push-notifikationer, når en ny besked oprettes i en tildelt samtale",
"HAS_ENABLED_PUSH": "Du har aktiveret push for denne browser.", "HAS_ENABLED_PUSH": "Du har aktiveret push for denne browser.",
"REQUEST_PUSH": "Aktivér push-notifikationer" "REQUEST_PUSH": "Aktivér push-notifikationer"
@ -118,10 +120,12 @@
"HOME": "Hjem", "HOME": "Hjem",
"AGENTS": "Agenter", "AGENTS": "Agenter",
"INBOXES": "Indbakker", "INBOXES": "Indbakker",
"NOTIFICATIONS": "Notifications",
"CANNED_RESPONSES": "Standardsvar Svar", "CANNED_RESPONSES": "Standardsvar Svar",
"INTEGRATIONS": "Integrationer", "INTEGRATIONS": "Integrationer",
"ACCOUNT_SETTINGS": "Kontoindstillinger", "ACCOUNT_SETTINGS": "Kontoindstillinger",
"LABELS": "Etiketter" "LABELS": "Etiketter",
"TEAMS": "Teams"
}, },
"CREATE_ACCOUNT": { "CREATE_ACCOUNT": {
"NEW_ACCOUNT": "Ny Konto", "NEW_ACCOUNT": "Ny Konto",

View File

@ -79,6 +79,7 @@
}, },
"RECEIVED_VIA_EMAIL": "Per E-Mail empfangen", "RECEIVED_VIA_EMAIL": "Per E-Mail empfangen",
"VIEW_TWEET_IN_TWITTER": "Tweet auf Twitter anzeigen", "VIEW_TWEET_IN_TWITTER": "Tweet auf Twitter anzeigen",
"REPLY_TO_TWEET": "Auf diesen Tweet antworten" "REPLY_TO_TWEET": "Auf diesen Tweet antworten",
"NO_MESSAGES": "No Messages"
} }
} }

View File

@ -25,6 +25,7 @@
"REMOVE_SELECTION": "Auswahl entfernen", "REMOVE_SELECTION": "Auswahl entfernen",
"DOWNLOAD": "Herunterladen", "DOWNLOAD": "Herunterladen",
"UPLOADING_ATTACHMENTS": "Uploading attachments...", "UPLOADING_ATTACHMENTS": "Uploading attachments...",
"NO_RESPONSE": "No response",
"HEADER": { "HEADER": {
"RESOLVE_ACTION": "Fall schließen", "RESOLVE_ACTION": "Fall schließen",
"REOPEN_ACTION": "Wieder öffnen", "REOPEN_ACTION": "Wieder öffnen",
@ -44,7 +45,8 @@
"TWEET": "Tweet", "TWEET": "Tweet",
"TIP_FORMAT_ICON": "Show rich text editor", "TIP_FORMAT_ICON": "Show rich text editor",
"TIP_EMOJI_ICON": "Show emoji selector", "TIP_EMOJI_ICON": "Show emoji selector",
"TIP_ATTACH_ICON": "Attach files" "TIP_ATTACH_ICON": "Attach files",
"ENTER_TO_SEND": "Enter to send"
}, },
"VISIBLE_TO_AGENTS": "Privater Hinweis: Nur für Sie und Ihr Team sichtbar", "VISIBLE_TO_AGENTS": "Privater Hinweis: Nur für Sie und Ihr Team sichtbar",
"CHANGE_STATUS": "Gesprächsstatus geändert", "CHANGE_STATUS": "Gesprächsstatus geändert",

View File

@ -51,5 +51,25 @@
"ENTER_TO_REMOVE": "Drücke Enter zum Entfernen", "ENTER_TO_REMOVE": "Drücke Enter zum Entfernen",
"SELECT_ONE": "Eine Option wählen" "SELECT_ONE": "Eine Option wählen"
} }
},
"NOTIFICATIONS_PAGE": {
"HEADER": "Notifications",
"MARK_ALL_DONE": "Mark All Done",
"LIST": {
"LOADING_MESSAGE": "Loading notifications...",
"404": "No Notifications",
"TABLE_HEADER": [
"Name",
"Telefonnummer",
"Gespräche",
"Letzter Kontakt"
]
},
"TYPE_LABEL": {
"conversation_creation": "New conversation",
"conversation_assignment": "Conversation Assigned",
"assigned_conversation_new_message": "New Message",
"conversation_mention": "Mention"
}
} }
} }

View File

@ -27,6 +27,7 @@
"NOTE": "Aktualisieren Sie hier Ihre E-Mail-Benachrichtigungseinstellungen", "NOTE": "Aktualisieren Sie hier Ihre E-Mail-Benachrichtigungseinstellungen",
"CONVERSATION_ASSIGNMENT": "Senden Sie E-Mail-Benachrichtigungen, wenn mir ein Gespräch zugewiesen wurde", "CONVERSATION_ASSIGNMENT": "Senden Sie E-Mail-Benachrichtigungen, wenn mir ein Gespräch zugewiesen wurde",
"CONVERSATION_CREATION": "Senden Sie E-Mail-Benachrichtigungen, wenn eine neue Konversation erstellt wird", "CONVERSATION_CREATION": "Senden Sie E-Mail-Benachrichtigungen, wenn eine neue Konversation erstellt wird",
"CONVERSATION_MENTION": "Send email notifications when you are mentioned in a conversation",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send email notifications when a new message is created in an assigned conversation" "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send email notifications when a new message is created in an assigned conversation"
}, },
"API": { "API": {
@ -38,6 +39,7 @@
"NOTE": "Aktualisieren Sie hier Ihre Push-Benachrichtigungseinstellungen", "NOTE": "Aktualisieren Sie hier Ihre Push-Benachrichtigungseinstellungen",
"CONVERSATION_ASSIGNMENT": "Push-Benachrichtigungen senden, wenn mir ein Gespräch zugewiesen wird", "CONVERSATION_ASSIGNMENT": "Push-Benachrichtigungen senden, wenn mir ein Gespräch zugewiesen wird",
"CONVERSATION_CREATION": "Push-Benachrichtigungen senden, wenn eine neue Konversation startet", "CONVERSATION_CREATION": "Push-Benachrichtigungen senden, wenn eine neue Konversation startet",
"CONVERSATION_MENTION": "Send push notifications when you are mentioned in a conversation",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send push notifications when a new message is created in an assigned conversation", "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send push notifications when a new message is created in an assigned conversation",
"HAS_ENABLED_PUSH": "Sie haben die Push-Benachrichtigung für diesen Browser aktiviert.", "HAS_ENABLED_PUSH": "Sie haben die Push-Benachrichtigung für diesen Browser aktiviert.",
"REQUEST_PUSH": "Push-Benachrichtigungen aktivieren" "REQUEST_PUSH": "Push-Benachrichtigungen aktivieren"
@ -118,10 +120,12 @@
"HOME": "Hauptseite", "HOME": "Hauptseite",
"AGENTS": "Agenten", "AGENTS": "Agenten",
"INBOXES": "Posteingänge", "INBOXES": "Posteingänge",
"NOTIFICATIONS": "Notifications",
"CANNED_RESPONSES": "Vorgefertigte Antworten", "CANNED_RESPONSES": "Vorgefertigte Antworten",
"INTEGRATIONS": "Integrationen", "INTEGRATIONS": "Integrationen",
"ACCOUNT_SETTINGS": "Kontoeinstellungen", "ACCOUNT_SETTINGS": "Kontoeinstellungen",
"LABELS": "Labels" "LABELS": "Labels",
"TEAMS": "Teams"
}, },
"CREATE_ACCOUNT": { "CREATE_ACCOUNT": {
"NEW_ACCOUNT": "New Account", "NEW_ACCOUNT": "New Account",

View File

@ -79,6 +79,7 @@
}, },
"RECEIVED_VIA_EMAIL": "Παραλήφθηκε από email", "RECEIVED_VIA_EMAIL": "Παραλήφθηκε από email",
"VIEW_TWEET_IN_TWITTER": "Προβολή του tweet στο Twitter", "VIEW_TWEET_IN_TWITTER": "Προβολή του tweet στο Twitter",
"REPLY_TO_TWEET": "Απάντηση στο tweet" "REPLY_TO_TWEET": "Απάντηση στο tweet",
"NO_MESSAGES": "Κανένα Μήνυμα"
} }
} }

View File

@ -25,6 +25,7 @@
"REMOVE_SELECTION": "Διαγραφή Επιλογής", "REMOVE_SELECTION": "Διαγραφή Επιλογής",
"DOWNLOAD": "Κατέβασμα", "DOWNLOAD": "Κατέβασμα",
"UPLOADING_ATTACHMENTS": "Ανέβασμα επισυναπτόμενων...", "UPLOADING_ATTACHMENTS": "Ανέβασμα επισυναπτόμενων...",
"NO_RESPONSE": "Καμία ανταπόκριση",
"HEADER": { "HEADER": {
"RESOLVE_ACTION": "Επίλυση", "RESOLVE_ACTION": "Επίλυση",
"REOPEN_ACTION": "Επαναφορά", "REOPEN_ACTION": "Επαναφορά",
@ -42,9 +43,10 @@
"SEND": "Αποστολή", "SEND": "Αποστολή",
"CREATE": "Σημείωση (Add Note)", "CREATE": "Σημείωση (Add Note)",
"TWEET": "Tweet", "TWEET": "Tweet",
"TIP_FORMAT_ICON": "Show rich text editor", "TIP_FORMAT_ICON": "Προβολή επεξεργαστή εμπλουτισμένου κειμένου",
"TIP_EMOJI_ICON": "Show emoji selector", "TIP_EMOJI_ICON": "Προβολή επιλογέα emoji",
"TIP_ATTACH_ICON": "Attach files" "TIP_ATTACH_ICON": "Επισύναψη αρχείων",
"ENTER_TO_SEND": "Εισαγωγή για αποστολή"
}, },
"VISIBLE_TO_AGENTS": "Ιδιωτική Σημείωση: Ορατή μόνο σε σας και την ομάδα σας", "VISIBLE_TO_AGENTS": "Ιδιωτική Σημείωση: Ορατή μόνο σε σας και την ομάδα σας",
"CHANGE_STATUS": "Η κατάσταση της συνομιλίας άλλαξε", "CHANGE_STATUS": "Η κατάσταση της συνομιλίας άλλαξε",

View File

@ -51,5 +51,25 @@
"ENTER_TO_REMOVE": "Πάτησε enter για αφαίρεση", "ENTER_TO_REMOVE": "Πάτησε enter για αφαίρεση",
"SELECT_ONE": "Επιλέξτε ένα" "SELECT_ONE": "Επιλέξτε ένα"
} }
},
"NOTIFICATIONS_PAGE": {
"HEADER": "Ειδοποιήσεις",
"MARK_ALL_DONE": "Όλες ολοκληρωμένες",
"LIST": {
"LOADING_MESSAGE": "Φόρτωση ειδοποιήσεων...",
"404": "Καμία Ειδοποίηση",
"TABLE_HEADER": [
"Όνομα",
"Αριθμός Τηλεφώνου",
"Συζητήσεις",
"Τελευταία επικοινωνία"
]
},
"TYPE_LABEL": {
"conversation_creation": "Νέα συνομιλία",
"conversation_assignment": "Η Συνομιλία Ανατέθηκε",
"assigned_conversation_new_message": "Νέο Μήνυμα",
"conversation_mention": "Αναφορά"
}
} }
} }

View File

@ -27,6 +27,7 @@
"NOTE": "Ενημέρωστε την προτίμηση για ειδοποιήσεις με email εδώ", "NOTE": "Ενημέρωστε την προτίμηση για ειδοποιήσεις με email εδώ",
"CONVERSATION_ASSIGNMENT": "Να στέλνεται ειδοποίηση email όταν μια συνομιλία αντιστοιχίζεται σε μένα", "CONVERSATION_ASSIGNMENT": "Να στέλνεται ειδοποίηση email όταν μια συνομιλία αντιστοιχίζεται σε μένα",
"CONVERSATION_CREATION": "Να στέλνεται ειδοποίηση όταν δημιουργείται μια νέα συνομιλία", "CONVERSATION_CREATION": "Να στέλνεται ειδοποίηση όταν δημιουργείται μια νέα συνομιλία",
"CONVERSATION_MENTION": "Αποστολή ειδοποιήσεων email όταν αναφέρεστε σε μια συνομιλία",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Αποστολή ειδοποίησης email όταν ένα νέο μήνυμα δημιουργείται σε συνομιλία που έχει αναληφθεί" "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Αποστολή ειδοποίησης email όταν ένα νέο μήνυμα δημιουργείται σε συνομιλία που έχει αναληφθεί"
}, },
"API": { "API": {
@ -38,6 +39,7 @@
"NOTE": "Ενημερώστε τις ρυθμίσεις για τις push ειδοποιήσεις εδώ", "NOTE": "Ενημερώστε τις ρυθμίσεις για τις push ειδοποιήσεις εδώ",
"CONVERSATION_ASSIGNMENT": "Να ειδοποιούμαι με push όταν μια συνομιλία μου ανατείθεται", "CONVERSATION_ASSIGNMENT": "Να ειδοποιούμαι με push όταν μια συνομιλία μου ανατείθεται",
"CONVERSATION_CREATION": "Να ειδοποιούμαι με push όταν δημιουργείται μια νέα συνομιλία", "CONVERSATION_CREATION": "Να ειδοποιούμαι με push όταν δημιουργείται μια νέα συνομιλία",
"CONVERSATION_MENTION": "Αποστολή ειδοποιήσεων push όταν αναφέρεστε σε μια συνομιλία",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Αποστολή ειδοποίησης push όταν ένα νέο μήνυμα δημιουργείται σε συνομιλία που έχει αναληφθεί", "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Αποστολή ειδοποίησης push όταν ένα νέο μήνυμα δημιουργείται σε συνομιλία που έχει αναληφθεί",
"HAS_ENABLED_PUSH": "Έχετε ενεργοποιήσει τις ειδοποιήσεις push για αυτόν τον browser.", "HAS_ENABLED_PUSH": "Έχετε ενεργοποιήσει τις ειδοποιήσεις push για αυτόν τον browser.",
"REQUEST_PUSH": "Ενεργοποποίηση των ειδοποιήσεων push" "REQUEST_PUSH": "Ενεργοποποίηση των ειδοποιήσεων push"
@ -118,10 +120,12 @@
"HOME": "Αρχική", "HOME": "Αρχική",
"AGENTS": "Πράκτορες", "AGENTS": "Πράκτορες",
"INBOXES": "Κιβώτια Εισερχομένων", "INBOXES": "Κιβώτια Εισερχομένων",
"NOTIFICATIONS": "Ειδοποιήσεις",
"CANNED_RESPONSES": "Έτοιμες Απαντήσεις", "CANNED_RESPONSES": "Έτοιμες Απαντήσεις",
"INTEGRATIONS": "Ενοποιήσεις", "INTEGRATIONS": "Ενοποιήσεις",
"ACCOUNT_SETTINGS": "Ρυθμίσεις Λογαριασμού", "ACCOUNT_SETTINGS": "Ρυθμίσεις Λογαριασμού",
"LABELS": "Ετικέτες" "LABELS": "Ετικέτες",
"TEAMS": "Teams"
}, },
"CREATE_ACCOUNT": { "CREATE_ACCOUNT": {
"NEW_ACCOUNT": "Νέος Λογαριασμός", "NEW_ACCOUNT": "Νέος Λογαριασμός",

View File

@ -79,6 +79,7 @@
}, },
"RECEIVED_VIA_EMAIL": "Received via email", "RECEIVED_VIA_EMAIL": "Received via email",
"VIEW_TWEET_IN_TWITTER": "View tweet in Twitter", "VIEW_TWEET_IN_TWITTER": "View tweet in Twitter",
"REPLY_TO_TWEET": "Reply to this tweet" "REPLY_TO_TWEET": "Reply to this tweet",
"NO_MESSAGES": "No Messages"
} }
} }

View File

@ -25,6 +25,7 @@
"REMOVE_SELECTION": "Remove Selection", "REMOVE_SELECTION": "Remove Selection",
"DOWNLOAD": "Download", "DOWNLOAD": "Download",
"UPLOADING_ATTACHMENTS": "Uploading attachments...", "UPLOADING_ATTACHMENTS": "Uploading attachments...",
"NO_RESPONSE": "No response",
"HEADER": { "HEADER": {
"RESOLVE_ACTION": "Resolve", "RESOLVE_ACTION": "Resolve",
"REOPEN_ACTION": "Reopen", "REOPEN_ACTION": "Reopen",
@ -44,7 +45,8 @@
"TWEET": "Tweet", "TWEET": "Tweet",
"TIP_FORMAT_ICON": "Show rich text editor", "TIP_FORMAT_ICON": "Show rich text editor",
"TIP_EMOJI_ICON": "Show emoji selector", "TIP_EMOJI_ICON": "Show emoji selector",
"TIP_ATTACH_ICON": "Attach files" "TIP_ATTACH_ICON": "Attach files",
"ENTER_TO_SEND": "Enter to send"
}, },
"VISIBLE_TO_AGENTS": "Private Note: Only visible to you and your team", "VISIBLE_TO_AGENTS": "Private Note: Only visible to you and your team",
"CHANGE_STATUS": "Conversation status changed", "CHANGE_STATUS": "Conversation status changed",

View File

@ -51,5 +51,25 @@
"ENTER_TO_REMOVE": "Press enter to remove", "ENTER_TO_REMOVE": "Press enter to remove",
"SELECT_ONE": "Select one" "SELECT_ONE": "Select one"
} }
},
"NOTIFICATIONS_PAGE": {
"HEADER": "Notifications",
"MARK_ALL_DONE": "Mark All Done",
"LIST": {
"LOADING_MESSAGE": "Loading notifications...",
"404": "No Notifications",
"TABLE_HEADER": [
"Name",
"Phone Number",
"Conversations",
"Last Contacted"
]
},
"TYPE_LABEL": {
"conversation_creation": "New conversation",
"conversation_assignment": "Conversation Assigned",
"assigned_conversation_new_message": "New Message",
"conversation_mention": "Mention"
}
} }
} }

View File

@ -38,7 +38,8 @@
"PICK_A_VALUE": "Pick a value" "PICK_A_VALUE": "Pick a value"
}, },
"TWITTER": { "TWITTER": {
"HELP": "To add your Twitter profile as a channel, you need to authenticate your Twitter Profile by clicking on 'Sign in with Twitter' " "HELP": "To add your Twitter profile as a channel, you need to authenticate your Twitter Profile by clicking on 'Sign in with Twitter' ",
"ERROR_MESSAGE": "There was an error connecting to Twitter, please try again"
}, },
"WEBSITE_CHANNEL": { "WEBSITE_CHANNEL": {
"TITLE": "Website channel", "TITLE": "Website channel",
@ -224,7 +225,8 @@
"TABS": { "TABS": {
"SETTINGS": "Settings", "SETTINGS": "Settings",
"COLLABORATORS": "Collaborators", "COLLABORATORS": "Collaborators",
"CONFIGURATION": "Configuration" "CONFIGURATION": "Configuration",
"PRE_CHAT_FORM": "Pre Chat Form"
}, },
"SETTINGS": "Settings", "SETTINGS": "Settings",
"FEATURES": { "FEATURES": {
@ -250,6 +252,23 @@
"SUBTITLE": "Your Facebook connection has expired, please reconnect your Facebook page to continue services", "SUBTITLE": "Your Facebook connection has expired, please reconnect your Facebook page to continue services",
"MESSAGE_SUCCESS": "Reconnection successful", "MESSAGE_SUCCESS": "Reconnection successful",
"MESSAGE_ERROR": "There was an error, please try again" "MESSAGE_ERROR": "There was an error, please try again"
},
"PRE_CHAT_FORM": {
"DESCRIPTION": "Pre chat forms enable you to capture user information before they start conversation with you.",
"ENABLE": {
"LABEL": "Enable pre chat form",
"OPTIONS": {
"ENABLED": "Yes",
"DISABLED": "No"
}
},
"PRE_CHAT_MESSAGE": {
"LABEL": "Pre Chat Message",
"PLACEHOLDER": "This message would be visible to the users along with the form"
},
"REQUIRE_EMAIL": {
"LABEL": "Visitors should provide their name and email address before starting the chat"
}
} }
} }
} }

View File

@ -27,6 +27,7 @@
"NOTE": "Update your email notification preferences here", "NOTE": "Update your email notification preferences here",
"CONVERSATION_ASSIGNMENT": "Send email notifications when a conversation is assigned to me", "CONVERSATION_ASSIGNMENT": "Send email notifications when a conversation is assigned to me",
"CONVERSATION_CREATION": "Send email notifications when a new conversation is created", "CONVERSATION_CREATION": "Send email notifications when a new conversation is created",
"CONVERSATION_MENTION": "Send email notifications when you are mentioned in a conversation",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send email notifications when a new message is created in an assigned conversation" "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send email notifications when a new message is created in an assigned conversation"
}, },
"API": { "API": {
@ -38,6 +39,7 @@
"NOTE": "Update your push notification preferences here", "NOTE": "Update your push notification preferences here",
"CONVERSATION_ASSIGNMENT": "Send push notifications when a conversation is assigned to me", "CONVERSATION_ASSIGNMENT": "Send push notifications when a conversation is assigned to me",
"CONVERSATION_CREATION": "Send push notifications when a new conversation is created", "CONVERSATION_CREATION": "Send push notifications when a new conversation is created",
"CONVERSATION_MENTION": "Send push notifications when you are mentioned in a conversation",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send push notifications when a new message is created in an assigned conversation", "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send push notifications when a new message is created in an assigned conversation",
"HAS_ENABLED_PUSH": "You have enabled push for this browser.", "HAS_ENABLED_PUSH": "You have enabled push for this browser.",
"REQUEST_PUSH": "Enable push notifications" "REQUEST_PUSH": "Enable push notifications"
@ -118,10 +120,12 @@
"HOME": "Home", "HOME": "Home",
"AGENTS": "Agents", "AGENTS": "Agents",
"INBOXES": "Inboxes", "INBOXES": "Inboxes",
"NOTIFICATIONS": "Notifications",
"CANNED_RESPONSES": "Canned Responses", "CANNED_RESPONSES": "Canned Responses",
"INTEGRATIONS": "Integrations", "INTEGRATIONS": "Integrations",
"ACCOUNT_SETTINGS": "Account Settings", "ACCOUNT_SETTINGS": "Account Settings",
"LABELS": "Labels" "LABELS": "Labels",
"TEAMS": "Teams"
}, },
"CREATE_ACCOUNT": { "CREATE_ACCOUNT": {
"NEW_ACCOUNT": "New Account", "NEW_ACCOUNT": "New Account",

View File

@ -79,6 +79,7 @@
}, },
"RECEIVED_VIA_EMAIL": "Recibido por email", "RECEIVED_VIA_EMAIL": "Recibido por email",
"VIEW_TWEET_IN_TWITTER": "Ver tweet en Twitter", "VIEW_TWEET_IN_TWITTER": "Ver tweet en Twitter",
"REPLY_TO_TWEET": "Responder a este tweet" "REPLY_TO_TWEET": "Responder a este tweet",
"NO_MESSAGES": "No Messages"
} }
} }

View File

@ -25,6 +25,7 @@
"REMOVE_SELECTION": "Eliminar selección", "REMOVE_SELECTION": "Eliminar selección",
"DOWNLOAD": "Descargar", "DOWNLOAD": "Descargar",
"UPLOADING_ATTACHMENTS": "Uploading attachments...", "UPLOADING_ATTACHMENTS": "Uploading attachments...",
"NO_RESPONSE": "No response",
"HEADER": { "HEADER": {
"RESOLVE_ACTION": "Resolver", "RESOLVE_ACTION": "Resolver",
"REOPEN_ACTION": "Reabrir", "REOPEN_ACTION": "Reabrir",
@ -44,7 +45,8 @@
"TWEET": "Tweet", "TWEET": "Tweet",
"TIP_FORMAT_ICON": "Show rich text editor", "TIP_FORMAT_ICON": "Show rich text editor",
"TIP_EMOJI_ICON": "Show emoji selector", "TIP_EMOJI_ICON": "Show emoji selector",
"TIP_ATTACH_ICON": "Attach files" "TIP_ATTACH_ICON": "Attach files",
"ENTER_TO_SEND": "Enter to send"
}, },
"VISIBLE_TO_AGENTS": "Nota privada: solo visible para ti y tu equipo", "VISIBLE_TO_AGENTS": "Nota privada: solo visible para ti y tu equipo",
"CHANGE_STATUS": "Estado de la conversación cambiado", "CHANGE_STATUS": "Estado de la conversación cambiado",

View File

@ -51,5 +51,25 @@
"ENTER_TO_REMOVE": "Press enter to remove", "ENTER_TO_REMOVE": "Press enter to remove",
"SELECT_ONE": "Select one" "SELECT_ONE": "Select one"
} }
},
"NOTIFICATIONS_PAGE": {
"HEADER": "Notificaciones",
"MARK_ALL_DONE": "Mark All Done",
"LIST": {
"LOADING_MESSAGE": "Loading notifications...",
"404": "No Notifications",
"TABLE_HEADER": [
"Nombre",
"Número telefónico",
"Conversaciones",
"Last Contacted"
]
},
"TYPE_LABEL": {
"conversation_creation": "New conversation",
"conversation_assignment": "Conversation Assigned",
"assigned_conversation_new_message": "New Message",
"conversation_mention": "Mention"
}
} }
} }

View File

@ -27,6 +27,7 @@
"NOTE": "Actualiza tus preferencias de notificación por correo electrónico aquí", "NOTE": "Actualiza tus preferencias de notificación por correo electrónico aquí",
"CONVERSATION_ASSIGNMENT": "Enviar notificaciones por correo electrónico cuando se me ha asignado una conversación", "CONVERSATION_ASSIGNMENT": "Enviar notificaciones por correo electrónico cuando se me ha asignado una conversación",
"CONVERSATION_CREATION": "Enviar notificaciones por correo electrónico cuando se crea una nueva conversación", "CONVERSATION_CREATION": "Enviar notificaciones por correo electrónico cuando se crea una nueva conversación",
"CONVERSATION_MENTION": "Send email notifications when you are mentioned in a conversation",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Envirar notificaciones por correo electrónico cuando un nuevo mensaje es creado en una conversación asignada" "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Envirar notificaciones por correo electrónico cuando un nuevo mensaje es creado en una conversación asignada"
}, },
"API": { "API": {
@ -38,6 +39,7 @@
"NOTE": "Actualize sus preferencias de notificaciones push aquí", "NOTE": "Actualize sus preferencias de notificaciones push aquí",
"CONVERSATION_ASSIGNMENT": "Enviar notificaciones push cuando se me ha asignado una conversación", "CONVERSATION_ASSIGNMENT": "Enviar notificaciones push cuando se me ha asignado una conversación",
"CONVERSATION_CREATION": "Enviar notificaciones push cuando se crea una nueva conversación", "CONVERSATION_CREATION": "Enviar notificaciones push cuando se crea una nueva conversación",
"CONVERSATION_MENTION": "Send push notifications when you are mentioned in a conversation",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Enviar notificaciones push cuando un nuevo mensaja es creadao en una conversación asignada", "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Enviar notificaciones push cuando un nuevo mensaja es creadao en una conversación asignada",
"HAS_ENABLED_PUSH": "Has habilitado notificaciones push para este navegador.", "HAS_ENABLED_PUSH": "Has habilitado notificaciones push para este navegador.",
"REQUEST_PUSH": "Habilitar notificaciones push" "REQUEST_PUSH": "Habilitar notificaciones push"
@ -118,10 +120,12 @@
"HOME": "Inicio", "HOME": "Inicio",
"AGENTS": "Agentes", "AGENTS": "Agentes",
"INBOXES": "Entradas", "INBOXES": "Entradas",
"NOTIFICATIONS": "Notificaciones",
"CANNED_RESPONSES": "Respuestas predefinidas", "CANNED_RESPONSES": "Respuestas predefinidas",
"INTEGRATIONS": "Integraciones", "INTEGRATIONS": "Integraciones",
"ACCOUNT_SETTINGS": "Configuración de la cuenta", "ACCOUNT_SETTINGS": "Configuración de la cuenta",
"LABELS": "Etiquetas" "LABELS": "Etiquetas",
"TEAMS": "Teams"
}, },
"CREATE_ACCOUNT": { "CREATE_ACCOUNT": {
"NEW_ACCOUNT": "Nueva cuenta", "NEW_ACCOUNT": "Nueva cuenta",

View File

@ -14,9 +14,9 @@
"ERROR": "Full name is too short" "ERROR": "Full name is too short"
}, },
"EMAIL": { "EMAIL": {
"LABEL": "Work email", "LABEL": "Email de trabajo",
"PLACEHOLDER": "Enter your work email address. eg: bruce@wayne.enterprises", "PLACEHOLDER": "Introduzca su dirección de correo electrónico de trabajo. Ejemplo: bruce@wayne.enterprises",
"ERROR": "Email address is invalid" "ERROR": "Dirección de correo no válida"
}, },
"PASSWORD": { "PASSWORD": {
"LABEL": "Contraseña", "LABEL": "Contraseña",
@ -33,6 +33,6 @@
"ERROR_MESSAGE": "No se pudo conectar al servidor Woot, por favor inténtalo de nuevo más tarde" "ERROR_MESSAGE": "No se pudo conectar al servidor Woot, por favor inténtalo de nuevo más tarde"
}, },
"SUBMIT": "Enviar", "SUBMIT": "Enviar",
"HAVE_AN_ACCOUNT": "Already have an account?" "HAVE_AN_ACCOUNT": "¿Ya tienes una cuenta?"
} }
} }

View File

@ -79,6 +79,7 @@
}, },
"RECEIVED_VIA_EMAIL": "از طریق ایمیل دریافت شد", "RECEIVED_VIA_EMAIL": "از طریق ایمیل دریافت شد",
"VIEW_TWEET_IN_TWITTER": "مشاهده توییت در توییتر", "VIEW_TWEET_IN_TWITTER": "مشاهده توییت در توییتر",
"REPLY_TO_TWEET": "پاسخ به این توییت" "REPLY_TO_TWEET": "پاسخ به این توییت",
"NO_MESSAGES": "هیچ پیامی وجود ندارد"
} }
} }

View File

@ -34,9 +34,9 @@
"NO_AVAILABLE_LABELS": "هیچ برچسبی به این گفتگو اضافه نشده است." "NO_AVAILABLE_LABELS": "هیچ برچسبی به این گفتگو اضافه نشده است."
}, },
"MUTE_CONTACT": "بی‌صدا کردن گفتگو", "MUTE_CONTACT": "بی‌صدا کردن گفتگو",
"UNMUTE_CONTACT": "Unmute Conversation", "UNMUTE_CONTACT": "خارج کردن از حالت بی صدا",
"MUTED_SUCCESS": "این گفتگو به مدت ۶ ساعت بی‌صدا است", "MUTED_SUCCESS": "این گفتگو به مدت ۶ ساعت بی‌صدا است",
"UNMUTED_SUCCESS": "This conversation is unmuted", "UNMUTED_SUCCESS": "این گفتگو از حالت بی صدا خارج شده است",
"SEND_TRANSCRIPT": "ارسال متن", "SEND_TRANSCRIPT": "ارسال متن",
"EDIT_LABEL": "ویرایش" "EDIT_LABEL": "ویرایش"
}, },
@ -108,7 +108,7 @@
"نام", "نام",
"شماره تلفن", "شماره تلفن",
"گفتگوها", "گفتگوها",
"Last Contacted" "آخرین تماس"
] ]
} }
} }

View File

@ -1,6 +1,6 @@
{ {
"CONVERSATION": { "CONVERSATION": {
"404": "لطفا یک گفتگو را از پنجره سمت چپ انتخاب کنید", "404": "لطفا یک گفتگو را از پنجره گفتگوها انتخاب کنید",
"NO_MESSAGE_1": "اوه اوه! به نظر می‌رسد هیچ پیامی از طرف مشتری در صندوق ورودی شما وجود ندارد.", "NO_MESSAGE_1": "اوه اوه! به نظر می‌رسد هیچ پیامی از طرف مشتری در صندوق ورودی شما وجود ندارد.",
"NO_MESSAGE_2": " برای ارسال پیام به صفحه خود بروید!", "NO_MESSAGE_2": " برای ارسال پیام به صفحه خود بروید!",
"NO_INBOX_1": "سلام! به نظر می‌رسد هنوز صندوق ورودی اضافه نکرده‌اید.", "NO_INBOX_1": "سلام! به نظر می‌رسد هنوز صندوق ورودی اضافه نکرده‌اید.",
@ -11,7 +11,7 @@
"TITLE": "جستجو پیام‌ها", "TITLE": "جستجو پیام‌ها",
"LOADING_MESSAGE": "درحال پردازش داده...", "LOADING_MESSAGE": "درحال پردازش داده...",
"PLACEHOLDER": "متنی برای جستجو پیام تایپ کنید", "PLACEHOLDER": "متنی برای جستجو پیام تایپ کنید",
"NO_MATCHING_RESULTS": "There are no messages matching the search parameters." "NO_MATCHING_RESULTS": "گفتگویی با مشخصات ورودی یافت نشد."
}, },
"UNREAD_MESSAGES": "پیام‌های خوانده نشده", "UNREAD_MESSAGES": "پیام‌های خوانده نشده",
"UNREAD_MESSAGE": "پیام خوانده نشده", "UNREAD_MESSAGE": "پیام خوانده نشده",
@ -25,6 +25,7 @@
"REMOVE_SELECTION": "حذف انتخاب‌شده‌ها", "REMOVE_SELECTION": "حذف انتخاب‌شده‌ها",
"DOWNLOAD": "دانلود", "DOWNLOAD": "دانلود",
"UPLOADING_ATTACHMENTS": "در حال بارگذاری پیوست‌ها...", "UPLOADING_ATTACHMENTS": "در حال بارگذاری پیوست‌ها...",
"NO_RESPONSE": "بدون پاسخ",
"HEADER": { "HEADER": {
"RESOLVE_ACTION": "حل شد", "RESOLVE_ACTION": "حل شد",
"REOPEN_ACTION": "دوباره باز کنید", "REOPEN_ACTION": "دوباره باز کنید",
@ -33,8 +34,8 @@
"DETAILS": "جزئیات" "DETAILS": "جزئیات"
}, },
"FOOTER": { "FOOTER": {
"MSG_INPUT": "Shift + enter for new line. Start with '/' to select a Canned Response.", "MSG_INPUT": "برای رفتن به سرخط shift+enter و برای استفاده از پیام های ذخیره شده / را بزنید.",
"PRIVATE_MSG_INPUT": "Shift + enter for new line. This will be visible only to Agents" "PRIVATE_MSG_INPUT": "برای رفتن به سرخط shift+enter را بزنید. این پیام فقط به ایجنت ها نمایش داده میشود"
}, },
"REPLYBOX": { "REPLYBOX": {
"REPLY": "پاسخ", "REPLY": "پاسخ",
@ -42,18 +43,19 @@
"SEND": "ارسال", "SEND": "ارسال",
"CREATE": "افزودن یادداشت", "CREATE": "افزودن یادداشت",
"TWEET": "توییت", "TWEET": "توییت",
"TIP_FORMAT_ICON": "Show rich text editor", "TIP_FORMAT_ICON": "نمایش ویرایشگر متنی پیشرفته",
"TIP_EMOJI_ICON": "Show emoji selector", "TIP_EMOJI_ICON": "انتخاب ایموجی",
"TIP_ATTACH_ICON": "Attach files" "TIP_ATTACH_ICON": "ضمیمه فایل",
"ENTER_TO_SEND": "برای ارسال Enter را بزنید"
}, },
"VISIBLE_TO_AGENTS": "یادداشت خصوصی: فقط برای شما و تیم شما قابل مشاهده است", "VISIBLE_TO_AGENTS": "یادداشت خصوصی: فقط برای شما و تیم شما قابل مشاهده است",
"CHANGE_STATUS": "وضعیت گفتگو تغییر کرد", "CHANGE_STATUS": "وضعیت گفتگو تغییر کرد",
"CHANGE_AGENT": "Conversation Assignee changed", "CHANGE_AGENT": "مسول گفتگو تغییر کرد",
"SENT_BY": "Sent by:", "SENT_BY": "ارسال شده توسط:",
"ASSIGNMENT": { "ASSIGNMENT": {
"SELECT_AGENT": "Select Agent", "SELECT_AGENT": "انتخاب ایجنت",
"REMOVE": "حذف", "REMOVE": "حذف",
"ASSIGN": "Assign" "ASSIGN": "اختصاص دادن"
} }
}, },
"EMAIL_TRANSCRIPT": { "EMAIL_TRANSCRIPT": {
@ -65,7 +67,7 @@
"SEND_EMAIL_ERROR": "خطایی پیش آمد. لطفا دوباره امتحان کنید", "SEND_EMAIL_ERROR": "خطایی پیش آمد. لطفا دوباره امتحان کنید",
"FORM": { "FORM": {
"SEND_TO_CONTACT": "متن گفتگو را برای مشتری ارسال کنید", "SEND_TO_CONTACT": "متن گفتگو را برای مشتری ارسال کنید",
"SEND_TO_AGENT": "Send the transcript to the assigned agent", "SEND_TO_AGENT": "متن گفتگو را به ایجنت اختصاص یافته ارسال کنید",
"SEND_TO_OTHER_EMAIL_ADDRESS": "متن گفتگو را به آدرس ایمیل دیگری ارسال کنید", "SEND_TO_OTHER_EMAIL_ADDRESS": "متن گفتگو را به آدرس ایمیل دیگری ارسال کنید",
"EMAIL": { "EMAIL": {
"PLACEHOLDER": "یک آدرس ایمیل وارد کنید", "PLACEHOLDER": "یک آدرس ایمیل وارد کنید",

View File

@ -34,22 +34,42 @@
"ERROR": "" "ERROR": ""
}, },
"AUTO_RESOLVE_DURATION": { "AUTO_RESOLVE_DURATION": {
"LABEL": "Number of days after a ticket should auto resolve if there is no activity", "LABEL": "تعداد روزهایی که اگر فعالیتی وجود نداشته باشد، گفتگو به صورت خودکار بسته شود",
"PLACEHOLDER": "30", "PLACEHOLDER": "30",
"ERROR": "Please enter a valid auto resolve duration (minimum 1 day)" "ERROR": "لطفاً یک مدت زمان حل خودکار معتبر (حداقل 1 روز) وارد کنید"
}, },
"FEATURES": { "FEATURES": {
"INBOUND_EMAIL_ENABLED": "Conversation continuity with emails is enabled for your account.", "INBOUND_EMAIL_ENABLED": "تداوم مکالمه با ایمیل برای حساب شما فعال است.",
"CUSTOM_EMAIL_DOMAIN_ENABLED": "اکنون می‌توانید ایمیل‌ها را در دامنه سفارشی خود دریافت کنید." "CUSTOM_EMAIL_DOMAIN_ENABLED": "اکنون می‌توانید ایمیل‌ها را در دامنه سفارشی خود دریافت کنید."
} }
}, },
"UPDATE_CHATWOOT": "An update %{latestChatwootVersion} for Chatwoot is available. Please update your instance." "UPDATE_CHATWOOT": "به روزرسانی%{latestChatwootVersion} برای Chatwoot در دسترس است. لطفا نمونه خود را به روز کنید."
}, },
"FORMS": { "FORMS": {
"MULTISELECT": { "MULTISELECT": {
"ENTER_TO_SELECT": "Press enter to select", "ENTER_TO_SELECT": "برای انتخاب Enter را فشار دهید",
"ENTER_TO_REMOVE": "Press enter to remove", "ENTER_TO_REMOVE": "برای حذف دکمه enter را فشار دهید",
"SELECT_ONE": "Select one" "SELECT_ONE": "یکی را انتخاب کن"
}
},
"NOTIFICATIONS_PAGE": {
"HEADER": "اعلان ها",
"MARK_ALL_DONE": "همه را به انجام شده تغییر بده",
"LIST": {
"LOADING_MESSAGE": "در حال بارگیری اعلان ها...",
"404": "اعلانی وجود ندارد",
"TABLE_HEADER": [
"نام",
"شماره تلفن",
"گفتگوها",
"آخرین تماس"
]
},
"TYPE_LABEL": {
"conversation_creation": "گفتگوی جدید",
"conversation_assignment": "مکالمه اختصاص داده شد",
"assigned_conversation_new_message": "پیام جدید",
"conversation_mention": "اشاره"
} }
} }
} }

View File

@ -75,10 +75,10 @@
}, },
"REPLY_TIME": { "REPLY_TIME": {
"TITLE": "تنظیم زمان پاسخ", "TITLE": "تنظیم زمان پاسخ",
"IN_A_FEW_MINUTES": "In a few minutes", "IN_A_FEW_MINUTES": "در چند دقیقه",
"IN_A_FEW_HOURS": "In a few hours", "IN_A_FEW_HOURS": "در چند ساعت",
"IN_A_DAY": "In a day", "IN_A_DAY": "در یک روز",
"HELP_TEXT": "This reply time will be displayed on the live chat widget" "HELP_TEXT": "این زمان پاسخ در ویجت چت زنده نمایش داده می شود"
}, },
"WIDGET_COLOR": { "WIDGET_COLOR": {
"LABEL": "رنگ ویجت", "LABEL": "رنگ ویجت",
@ -124,7 +124,7 @@
}, },
"API_CHANNEL": { "API_CHANNEL": {
"TITLE": "کانال API", "TITLE": "کانال API",
"DESC": "Integrate with API channel and start supporting your customers.", "DESC": "با کانال API ادغام شده و از مشتریان خود پشتیبانی کنید.",
"CHANNEL_NAME": { "CHANNEL_NAME": {
"LABEL": "عنوان کانال", "LABEL": "عنوان کانال",
"PLACEHOLDER": "لطفا اسم یک کانال را وارد کنید", "PLACEHOLDER": "لطفا اسم یک کانال را وارد کنید",
@ -132,12 +132,12 @@
}, },
"WEBHOOK_URL": { "WEBHOOK_URL": {
"LABEL": "آدرس URL وب هوک", "LABEL": "آدرس URL وب هوک",
"SUBTITLE": "Configure the URL where you want to recieve callbacks on events.", "SUBTITLE": "آدرس کال بک خود را جهت دریافت اطلاعات رویداد های وارد کنید.",
"PLACEHOLDER": "آدرس URL وب هوک" "PLACEHOLDER": "آدرس URL وب هوک"
}, },
"SUBMIT_BUTTON": "ایجاد کانال API", "SUBMIT_BUTTON": "ایجاد کانال API",
"API": { "API": {
"ERROR_MESSAGE": "We were not able to save the api channel" "ERROR_MESSAGE": "کانال API ذخیره نشد"
} }
}, },
"EMAIL_CHANNEL": { "EMAIL_CHANNEL": {
@ -150,14 +150,14 @@
}, },
"EMAIL": { "EMAIL": {
"LABEL": "ایمیل", "LABEL": "ایمیل",
"SUBTITLE": "Email where your customers sends you support tickets", "SUBTITLE": "آدرس ایمیلی که مشتریان درخواست پشتیبانی را برای شما ارسال کنند",
"PLACEHOLDER": "ایمیل" "PLACEHOLDER": "ایمیل"
}, },
"SUBMIT_BUTTON": "ایجاد کانال ایمیل", "SUBMIT_BUTTON": "ایجاد کانال ایمیل",
"API": { "API": {
"ERROR_MESSAGE": "We were not able to save the email channel" "ERROR_MESSAGE": "کانال ایمیل ذخیره نشد"
}, },
"FINISH_MESSAGE": "Start forwarding your emails to the following email address." "FINISH_MESSAGE": "ایمیل های خود را به این آدرس ها فوروارد کنید."
}, },
"AUTH": { "AUTH": {
"TITLE": "کانال‌ها", "TITLE": "کانال‌ها",
@ -166,8 +166,8 @@
"AGENTS": { "AGENTS": {
"TITLE": "اپراتورها", "TITLE": "اپراتورها",
"DESC": "در اینجا می‌توانید اپراتورها را به صندوق‌ ورودی خود اختصاص دهید. توجه داشته باشید که فقط اپراتورهایی که در اینجا معرفی شده باشند می‌توانند به پیام‌های این صندوق پاسخ بدهند.دیگر اپراتورها نخواهند توانست پیام‌های این صندوق را ببینید یا به آن‌ها پاسخی بدهند. <br> <b>پانویس:</b>به عنوان مدیر اگر می‌خواهید به همه صندوق‌های ورودی دسترسی داشته باشید می‌بایست خود را به عنوان اپراتور به همه صندوق‌ها اضافه کنید.", "DESC": "در اینجا می‌توانید اپراتورها را به صندوق‌ ورودی خود اختصاص دهید. توجه داشته باشید که فقط اپراتورهایی که در اینجا معرفی شده باشند می‌توانند به پیام‌های این صندوق پاسخ بدهند.دیگر اپراتورها نخواهند توانست پیام‌های این صندوق را ببینید یا به آن‌ها پاسخی بدهند. <br> <b>پانویس:</b>به عنوان مدیر اگر می‌خواهید به همه صندوق‌های ورودی دسترسی داشته باشید می‌بایست خود را به عنوان اپراتور به همه صندوق‌ها اضافه کنید.",
"VALIDATION_ERROR": "Add atleast one agent to your new Inbox", "VALIDATION_ERROR": "حداقل یک ایجنت به اینباکس جدید اضافه کنید",
"PICK_AGENTS": "Pick agents for the inbox" "PICK_AGENTS": "انتخاب ایجنت ها برای این صندوق ورودی"
}, },
"DETAILS": { "DETAILS": {
"TITLE": "جزئیات صندوق‌ ورودی", "TITLE": "جزئیات صندوق‌ ورودی",
@ -229,8 +229,8 @@
"SETTINGS": "تنظیمات", "SETTINGS": "تنظیمات",
"FEATURES": { "FEATURES": {
"LABEL": "امکانات", "LABEL": "امکانات",
"DISPLAY_FILE_PICKER": "Display file picker on the widget", "DISPLAY_FILE_PICKER": "نمایش گزینه ضمیه فایل در ویجت",
"DISPLAY_EMOJI_PICKER": "Display emoji picker on the widget" "DISPLAY_EMOJI_PICKER": "نمایش گزینه انتخاب ایموجی در ویجت"
}, },
"SETTINGS_POPUP": { "SETTINGS_POPUP": {
"MESSENGER_HEADING": "اسکریپت ویجت", "MESSENGER_HEADING": "اسکریپت ویجت",
@ -242,13 +242,13 @@
"INBOX_UPDATE_TITLE": "تنظیمات صندوق ورودی", "INBOX_UPDATE_TITLE": "تنظیمات صندوق ورودی",
"INBOX_UPDATE_SUB_TEXT": "تغییر پارامترهای صندوق ورودی", "INBOX_UPDATE_SUB_TEXT": "تغییر پارامترهای صندوق ورودی",
"AUTO_ASSIGNMENT_SUB_TEXT": "فعال کردن یا غیرفعال کردن واگذاری خودکار گفتگوها به اپراتورهای عضو این صندوق ورودی.", "AUTO_ASSIGNMENT_SUB_TEXT": "فعال کردن یا غیرفعال کردن واگذاری خودکار گفتگوها به اپراتورهای عضو این صندوق ورودی.",
"HMAC_VERIFICATION": "User Identity Validation", "HMAC_VERIFICATION": "اعتبار سنجی کاربر",
"HMAC_DESCRIPTION": "Inorder validate the users identity, the SDK allows you to pass an `identity_hash` for each user. You can generate HMAC using 'sha256' with the key shown here." "HMAC_DESCRIPTION": "برای اعتبار سنجی کاربران با استفاده از این کلید یک هش HMAC sha256 تولید کنید."
}, },
"FACEBOOK_REAUTHORIZE": { "FACEBOOK_REAUTHORIZE": {
"TITLE": "احراز هویت مجدد", "TITLE": "احراز هویت مجدد",
"SUBTITLE": "Your Facebook connection has expired, please reconnect your Facebook page to continue services", "SUBTITLE": "اتصال فیس بوک شما منقضی شده است ، لطفاً برای ادامه خدمات دوباره صفحه فیس بوک خود را متصل کنید",
"MESSAGE_SUCCESS": "Reconnection successful", "MESSAGE_SUCCESS": "اتصال مجدد موفقیت آمیز بود",
"MESSAGE_ERROR": "خطایی پیش آمد. لطفا دوباره امتحان کنید" "MESSAGE_ERROR": "خطایی پیش آمد. لطفا دوباره امتحان کنید"
} }
} }

View File

@ -4,7 +4,7 @@
"HEADER_BTN_TXT": "افزودن برچسب", "HEADER_BTN_TXT": "افزودن برچسب",
"LOADING": "درحال گرفتن برچسب‌ها", "LOADING": "درحال گرفتن برچسب‌ها",
"SEARCH_404": "هیچ آیتمی با این مشخصات یافت نشد", "SEARCH_404": "هیچ آیتمی با این مشخصات یافت نشد",
"SIDEBAR_TXT": "<p><b>Labels</b> <p>Labels help you to categorize conversations and prioritize them. You can assign label to a conversation from the sidepanel. <br /><br />Labels are tied to the account and can be used to create custom workflows in your organization. You can assign custom color to a label, it makes it easier to identify the label. You will be able to display the label on the sidebar to filter the conversations easily.</p>", "SIDEBAR_TXT": "<p>\n <b> برچسب ها</b>\n<p> برچسب ها به شما در دسته بندی مکالمات و اولویت بندی آنها کمک می کند. می توانید برچسب را به یک مکالمه از نوار کناری اختصاص دهید.\n <br/>\n <br/>\n برچسب ها به حساب گره خورده اند و می توانند برای ایجاد گردش کار سفارشی در سازمان شما استفاده شوند. می توانید رنگ سفارشی را به برچسب اختصاص دهید ، شناسایی برچسب آسان تر می شود. شما می توانید برچسب را در نوار کناری نمایش دهید تا مکالمه ها به راحتی فیلتر شود.\n</p>",
"LIST": { "LIST": {
"404": "هیچ برچسبی در این حساب‌کاربری وجود ندارد.", "404": "هیچ برچسبی در این حساب‌کاربری وجود ندارد.",
"TITLE": "مدیریت برچسب‌ها", "TITLE": "مدیریت برچسب‌ها",

View File

@ -3,7 +3,7 @@
"HEADER": "گزارشات", "HEADER": "گزارشات",
"LOADING_CHART": "در حال دریافت اطلاعات...", "LOADING_CHART": "در حال دریافت اطلاعات...",
"NO_ENOUGH_DATA": "متاسفانه اطلاعات کافی دریافت نشد، لطفا بعدا دوباره امتحان کنید", "NO_ENOUGH_DATA": "متاسفانه اطلاعات کافی دریافت نشد، لطفا بعدا دوباره امتحان کنید",
"DOWNLOAD_AGENT_REPORTS": "Download agent reports", "DOWNLOAD_AGENT_REPORTS": "دانلود گزارش ایجنت",
"METRICS": { "METRICS": {
"CONVERSATIONS": { "CONVERSATIONS": {
"NAME": "گفتگوها", "NAME": "گفتگوها",

View File

@ -27,7 +27,8 @@
"NOTE": "اینجا می‌توانید تنظیمات اعلامیه‌هایی که به ایمیل ارسال می‌شود تغییر دهید", "NOTE": "اینجا می‌توانید تنظیمات اعلامیه‌هایی که به ایمیل ارسال می‌شود تغییر دهید",
"CONVERSATION_ASSIGNMENT": "هر وقت گفتگویی به من اختصاص داده شد،‌ ایمیل بفرست", "CONVERSATION_ASSIGNMENT": "هر وقت گفتگویی به من اختصاص داده شد،‌ ایمیل بفرست",
"CONVERSATION_CREATION": "هر وقت گفتگوی جدیدی شروع شد برای من ایمیل بفرست", "CONVERSATION_CREATION": "هر وقت گفتگوی جدیدی شروع شد برای من ایمیل بفرست",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send email notifications when a new message is created in an assigned conversation" "CONVERSATION_MENTION": "هنگامی که از شما در گفتگو نام برده می‌شود، از طریق ایمیل آگاه‌سازی ارسال کن",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "با ایجاد پیام جدید در مکالمه اختصاصی ، اعلان های ایمیل را ارسال کنید"
}, },
"API": { "API": {
"UPDATE_SUCCESS": "تغییرات تنظیمات اعلامیه‌ها با موفقیت ثبت شد", "UPDATE_SUCCESS": "تغییرات تنظیمات اعلامیه‌ها با موفقیت ثبت شد",
@ -38,7 +39,8 @@
"NOTE": "اینجا می‌توانید تنظیمات پوش نوتیفیکیشن را تغییر دهید", "NOTE": "اینجا می‌توانید تنظیمات پوش نوتیفیکیشن را تغییر دهید",
"CONVERSATION_ASSIGNMENT": "هر وقت گفتگویی به من اختصاص داده شد، برای من پوش نوتیفیکیشن بفرست", "CONVERSATION_ASSIGNMENT": "هر وقت گفتگویی به من اختصاص داده شد، برای من پوش نوتیفیکیشن بفرست",
"CONVERSATION_CREATION": "هر وقت گفتگوی جدیدی شروع شد برای من پوش نوتیفیکیشن بفرست", "CONVERSATION_CREATION": "هر وقت گفتگوی جدیدی شروع شد برای من پوش نوتیفیکیشن بفرست",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Send push notifications when a new message is created in an assigned conversation", "CONVERSATION_MENTION": "هنگامی که از شما در مکالمه نام برده می شود پوش نوتیفیکیشن ارسال کنید",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "وقتی پیام جدیدی در مکالمه اختصاص داده شده ایجاد می شود ، پوش نوتیفیکیشن را ارسال کنید",
"HAS_ENABLED_PUSH": "در این مرورگر پوش نوتیفیکیشن را فعال کرده‌اید", "HAS_ENABLED_PUSH": "در این مرورگر پوش نوتیفیکیشن را فعال کرده‌اید",
"REQUEST_PUSH": "فعال کردن پوش نوتیفیکیشن" "REQUEST_PUSH": "فعال کردن پوش نوتیفیکیشن"
}, },
@ -118,10 +120,12 @@
"HOME": "صفحه اصلی", "HOME": "صفحه اصلی",
"AGENTS": "اپراتورها", "AGENTS": "اپراتورها",
"INBOXES": "صندوق‌های ورودی", "INBOXES": "صندوق‌های ورودی",
"NOTIFICATIONS": "اعلان ها",
"CANNED_RESPONSES": "پاسخ‌های آماده", "CANNED_RESPONSES": "پاسخ‌های آماده",
"INTEGRATIONS": "برنامه‌های تلفیق شده", "INTEGRATIONS": "برنامه‌های تلفیق شده",
"ACCOUNT_SETTINGS": "تنظیمات حساب", "ACCOUNT_SETTINGS": "تنظیمات حساب",
"LABELS": "برچسب‌ها" "LABELS": "برچسب‌ها",
"TEAMS": "Teams"
}, },
"CREATE_ACCOUNT": { "CREATE_ACCOUNT": {
"NEW_ACCOUNT": "حساب‌کاربری جدید", "NEW_ACCOUNT": "حساب‌کاربری جدید",

View File

@ -2,11 +2,11 @@
"REGISTER": { "REGISTER": {
"TRY_WOOT": "ثبت حساب", "TRY_WOOT": "ثبت حساب",
"TITLE": "ثبت نام", "TITLE": "ثبت نام",
"TERMS_ACCEPT": "با ثبت نام، اعلام می‌دارید که <a href=\"https://www.chatwoot.com/terms\">قوانین</a> و <a href=\"https://www.chatwoot.com/privacy-policy\">شرایط استفاده</a> از ما را تایید کرده و می‌پذیرید.", "TERMS_ACCEPT": "با ثبت نام، اعلام می‌دارید که <a href=\"https://www.chatwoot.com/terms\">قوانین</a> و <a href=\"https://www.chatwoot.com/privacy-policy\">شرایط استفاده</a> از این نرم افزار را تایید کرده و می‌پذیرید",
"ACCOUNT_NAME": { "ACCOUNT_NAME": {
"LABEL": "عنوان حساب", "LABEL": "عنوان حساب",
"PLACEHOLDER": "نام حساب‌کاربری را وارد کنید. به عنوان مثال: شرکت وین", "PLACEHOLDER": "نام حساب ‌کاربری را وارد کنید. به عنوان مثال: شرکت وین",
"ERROR": "نام حساب‌کاربری خیلی کوتاه است" "ERROR": "نام حساب ‌کاربری خیلی کوتاه است"
}, },
"FULL_NAME": { "FULL_NAME": {
"LABEL": "نام کامل", "LABEL": "نام کامل",
@ -33,6 +33,6 @@
"ERROR_MESSAGE": "ارتباط با سرور برقرار نشد، لطفا بعدا امتحان کنید" "ERROR_MESSAGE": "ارتباط با سرور برقرار نشد، لطفا بعدا امتحان کنید"
}, },
"SUBMIT": "ثبت", "SUBMIT": "ثبت",
"HAVE_AN_ACCOUNT": "از قبل حساب‌کاربری دارید؟" "HAVE_AN_ACCOUNT": "از قبل حساب ‌کاربری دارید؟"
} }
} }

View File

@ -79,6 +79,7 @@
}, },
"RECEIVED_VIA_EMAIL": "Vastaanotettu sähköpostitse", "RECEIVED_VIA_EMAIL": "Vastaanotettu sähköpostitse",
"VIEW_TWEET_IN_TWITTER": "Näytä twiitti Twitterissä", "VIEW_TWEET_IN_TWITTER": "Näytä twiitti Twitterissä",
"REPLY_TO_TWEET": "Vastaa tähän twiittiin" "REPLY_TO_TWEET": "Vastaa tähän twiittiin",
"NO_MESSAGES": "No Messages"
} }
} }

View File

@ -25,6 +25,7 @@
"REMOVE_SELECTION": "Poista valinnat", "REMOVE_SELECTION": "Poista valinnat",
"DOWNLOAD": "Lataa", "DOWNLOAD": "Lataa",
"UPLOADING_ATTACHMENTS": "Ladataan liitteitä...", "UPLOADING_ATTACHMENTS": "Ladataan liitteitä...",
"NO_RESPONSE": "No response",
"HEADER": { "HEADER": {
"RESOLVE_ACTION": "Ratkaise", "RESOLVE_ACTION": "Ratkaise",
"REOPEN_ACTION": "Uudelleenavaa", "REOPEN_ACTION": "Uudelleenavaa",
@ -44,7 +45,8 @@
"TWEET": "Twiittaa", "TWEET": "Twiittaa",
"TIP_FORMAT_ICON": "Show rich text editor", "TIP_FORMAT_ICON": "Show rich text editor",
"TIP_EMOJI_ICON": "Show emoji selector", "TIP_EMOJI_ICON": "Show emoji selector",
"TIP_ATTACH_ICON": "Attach files" "TIP_ATTACH_ICON": "Attach files",
"ENTER_TO_SEND": "Enter to send"
}, },
"VISIBLE_TO_AGENTS": "Yksityinen huomautus: Näkyy vain sinulle ja tiimillesi", "VISIBLE_TO_AGENTS": "Yksityinen huomautus: Näkyy vain sinulle ja tiimillesi",
"CHANGE_STATUS": "Keskustelun tila muutettu", "CHANGE_STATUS": "Keskustelun tila muutettu",

View File

@ -51,5 +51,25 @@
"ENTER_TO_REMOVE": "Paina enter poistaaksesi", "ENTER_TO_REMOVE": "Paina enter poistaaksesi",
"SELECT_ONE": "Valitse yksi" "SELECT_ONE": "Valitse yksi"
} }
},
"NOTIFICATIONS_PAGE": {
"HEADER": "Ilmoitukset",
"MARK_ALL_DONE": "Mark All Done",
"LIST": {
"LOADING_MESSAGE": "Loading notifications...",
"404": "No Notifications",
"TABLE_HEADER": [
"Nimi",
"Puhelinnumero",
"Keskustelut",
"Viimeksi otettu yhteyttä"
]
},
"TYPE_LABEL": {
"conversation_creation": "New conversation",
"conversation_assignment": "Conversation Assigned",
"assigned_conversation_new_message": "New Message",
"conversation_mention": "Mention"
}
} }
} }

View File

@ -27,6 +27,7 @@
"NOTE": "Päivitä sähköposti-ilmoitusasetukset täällä", "NOTE": "Päivitä sähköposti-ilmoitusasetukset täällä",
"CONVERSATION_ASSIGNMENT": "Lähetä sähköposti-ilmoitus, kun keskustelu on määritetty minulle", "CONVERSATION_ASSIGNMENT": "Lähetä sähköposti-ilmoitus, kun keskustelu on määritetty minulle",
"CONVERSATION_CREATION": "Lähetä sähköposti-ilmoitus, kun keskustelu on määritetty minulle", "CONVERSATION_CREATION": "Lähetä sähköposti-ilmoitus, kun keskustelu on määritetty minulle",
"CONVERSATION_MENTION": "Send email notifications when you are mentioned in a conversation",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Lähetä sähköposti-ilmoitus, kun uusi viesti saapuu minulle osoitettuun keskusteluun" "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Lähetä sähköposti-ilmoitus, kun uusi viesti saapuu minulle osoitettuun keskusteluun"
}, },
"API": { "API": {
@ -38,6 +39,7 @@
"NOTE": "Päivitä push-ilmoitusasetukset täällä", "NOTE": "Päivitä push-ilmoitusasetukset täällä",
"CONVERSATION_ASSIGNMENT": "Lähetä push-ilmoitus, kun keskustelu on määritetty minulle", "CONVERSATION_ASSIGNMENT": "Lähetä push-ilmoitus, kun keskustelu on määritetty minulle",
"CONVERSATION_CREATION": "Lähetä push-ilmoitus, kun keskustelu on määritetty minulle", "CONVERSATION_CREATION": "Lähetä push-ilmoitus, kun keskustelu on määritetty minulle",
"CONVERSATION_MENTION": "Send push notifications when you are mentioned in a conversation",
"ASSIGNED_CONVERSATION_NEW_MESSAGE": "Lähetä push-ilmoitus, kun uusi viesti saapuu minulle osoitettuun keskusteluun", "ASSIGNED_CONVERSATION_NEW_MESSAGE": "Lähetä push-ilmoitus, kun uusi viesti saapuu minulle osoitettuun keskusteluun",
"HAS_ENABLED_PUSH": "Tämän selaimen push-ilmoitukset on otettu käyttöön.", "HAS_ENABLED_PUSH": "Tämän selaimen push-ilmoitukset on otettu käyttöön.",
"REQUEST_PUSH": "Ota push-ilmoitukset käyttöön" "REQUEST_PUSH": "Ota push-ilmoitukset käyttöön"
@ -118,10 +120,12 @@
"HOME": "Koti", "HOME": "Koti",
"AGENTS": "Edustajat", "AGENTS": "Edustajat",
"INBOXES": "Kansiot", "INBOXES": "Kansiot",
"NOTIFICATIONS": "Ilmoitukset",
"CANNED_RESPONSES": "Tallennetut vastaukset", "CANNED_RESPONSES": "Tallennetut vastaukset",
"INTEGRATIONS": "Integraatiot", "INTEGRATIONS": "Integraatiot",
"ACCOUNT_SETTINGS": "Tilin asetukset", "ACCOUNT_SETTINGS": "Tilin asetukset",
"LABELS": "Tunnisteet" "LABELS": "Tunnisteet",
"TEAMS": "Teams"
}, },
"CREATE_ACCOUNT": { "CREATE_ACCOUNT": {
"NEW_ACCOUNT": "Uusi tili", "NEW_ACCOUNT": "Uusi tili",

View File

@ -79,6 +79,7 @@
}, },
"RECEIVED_VIA_EMAIL": "Reçu par courriel", "RECEIVED_VIA_EMAIL": "Reçu par courriel",
"VIEW_TWEET_IN_TWITTER": "Voir le tweet sur Twitter", "VIEW_TWEET_IN_TWITTER": "Voir le tweet sur Twitter",
"REPLY_TO_TWEET": "Répondre à ce tweet" "REPLY_TO_TWEET": "Répondre à ce tweet",
"NO_MESSAGES": "No Messages"
} }
} }

Some files were not shown because too many files have changed in this diff Show More