Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e3375fb84 | ||
|
|
58ce9bfc19 | ||
|
|
63a071f0d3 | ||
|
|
f0d3ae09d9 |
@@ -15,7 +15,7 @@ gem 'browser'
|
|||||||
gem 'hashie'
|
gem 'hashie'
|
||||||
gem 'jbuilder'
|
gem 'jbuilder'
|
||||||
gem 'kaminari'
|
gem 'kaminari'
|
||||||
gem 'responders', '>= 3.1.1'
|
gem 'responders'
|
||||||
gem 'rest-client'
|
gem 'rest-client'
|
||||||
gem 'telephone_number'
|
gem 'telephone_number'
|
||||||
gem 'time_diff'
|
gem 'time_diff'
|
||||||
@@ -67,7 +67,7 @@ gem 'webpacker'
|
|||||||
gem 'barnes'
|
gem 'barnes'
|
||||||
|
|
||||||
##--- gems for authentication & authorization ---##
|
##--- gems for authentication & authorization ---##
|
||||||
gem 'devise', '>= 4.9.3'
|
gem 'devise'
|
||||||
gem 'devise-secure_password', git: 'https://github.com/chatwoot/devise-secure_password', branch: 'chatwoot'
|
gem 'devise-secure_password', git: 'https://github.com/chatwoot/devise-secure_password', branch: 'chatwoot'
|
||||||
gem 'devise_token_auth'
|
gem 'devise_token_auth'
|
||||||
# authorization
|
# authorization
|
||||||
@@ -92,7 +92,7 @@ gem 'twitty', '~> 0.1.5'
|
|||||||
# facebook client
|
# facebook client
|
||||||
gem 'koala'
|
gem 'koala'
|
||||||
# slack client
|
# slack client
|
||||||
gem 'slack-ruby-client', '~> 2.2.0'
|
gem 'slack-ruby-client', '~> 2.0.0'
|
||||||
# for dialogflow integrations
|
# for dialogflow integrations
|
||||||
gem 'google-cloud-dialogflow-v2'
|
gem 'google-cloud-dialogflow-v2'
|
||||||
gem 'grpc'
|
gem 'grpc'
|
||||||
@@ -109,9 +109,9 @@ gem 'elastic-apm', require: false
|
|||||||
gem 'newrelic_rpm', require: false
|
gem 'newrelic_rpm', require: false
|
||||||
gem 'newrelic-sidekiq-metrics', require: false
|
gem 'newrelic-sidekiq-metrics', require: false
|
||||||
gem 'scout_apm', require: false
|
gem 'scout_apm', require: false
|
||||||
gem 'sentry-rails', '>= 5.12.0', require: false
|
gem 'sentry-rails', '>= 5.11.0', require: false
|
||||||
gem 'sentry-ruby', require: false
|
gem 'sentry-ruby', require: false
|
||||||
gem 'sentry-sidekiq', '>= 5.12.0', require: false
|
gem 'sentry-sidekiq', '>= 5.11.0', require: false
|
||||||
|
|
||||||
##-- background job processing --##
|
##-- background job processing --##
|
||||||
gem 'sidekiq', '>= 7.1.3'
|
gem 'sidekiq', '>= 7.1.3'
|
||||||
@@ -154,7 +154,7 @@ gem 'stripe'
|
|||||||
gem 'faker'
|
gem 'faker'
|
||||||
|
|
||||||
# Include logrange conditionally in intializer using env variable
|
# Include logrange conditionally in intializer using env variable
|
||||||
gem 'lograge', '~> 0.14.0', require: false
|
gem 'lograge', '~> 0.13.0', require: false
|
||||||
|
|
||||||
# worked with microsoft refresh token
|
# worked with microsoft refresh token
|
||||||
gem 'omniauth-oauth2'
|
gem 'omniauth-oauth2'
|
||||||
|
|||||||
+21
-20
@@ -148,7 +148,7 @@ GEM
|
|||||||
barnes (0.0.9)
|
barnes (0.0.9)
|
||||||
multi_json (~> 1)
|
multi_json (~> 1)
|
||||||
statsd-ruby (~> 1.1)
|
statsd-ruby (~> 1.1)
|
||||||
bcrypt (3.1.19)
|
bcrypt (3.1.18)
|
||||||
bindex (0.8.1)
|
bindex (0.8.1)
|
||||||
blingfire (0.1.8)
|
blingfire (0.1.8)
|
||||||
bootsnap (1.16.0)
|
bootsnap (1.16.0)
|
||||||
@@ -193,7 +193,7 @@ GEM
|
|||||||
irb (>= 1.5.0)
|
irb (>= 1.5.0)
|
||||||
reline (>= 0.3.1)
|
reline (>= 0.3.1)
|
||||||
declarative (0.0.20)
|
declarative (0.0.20)
|
||||||
devise (4.9.3)
|
devise (4.9.2)
|
||||||
bcrypt (~> 3.0)
|
bcrypt (~> 3.0)
|
||||||
orm_adapter (~> 0.1)
|
orm_adapter (~> 0.1)
|
||||||
railties (>= 4.1.0)
|
railties (>= 4.1.0)
|
||||||
@@ -273,7 +273,7 @@ GEM
|
|||||||
googleauth (~> 1.0)
|
googleauth (~> 1.0)
|
||||||
grpc (~> 1.36)
|
grpc (~> 1.36)
|
||||||
geocoder (1.8.1)
|
geocoder (1.8.1)
|
||||||
gli (2.21.1)
|
gli (2.21.0)
|
||||||
globalid (1.2.1)
|
globalid (1.2.1)
|
||||||
activesupport (>= 6.1)
|
activesupport (>= 6.1)
|
||||||
gmail_xoauth (0.4.2)
|
gmail_xoauth (0.4.2)
|
||||||
@@ -434,12 +434,12 @@ GEM
|
|||||||
llhttp-ffi (0.4.0)
|
llhttp-ffi (0.4.0)
|
||||||
ffi-compiler (~> 1.0)
|
ffi-compiler (~> 1.0)
|
||||||
rake (~> 13.0)
|
rake (~> 13.0)
|
||||||
lograge (0.14.0)
|
lograge (0.13.0)
|
||||||
actionpack (>= 4)
|
actionpack (>= 4)
|
||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
railties (>= 4)
|
railties (>= 4)
|
||||||
request_store (~> 1.0)
|
request_store (~> 1.0)
|
||||||
loofah (2.21.4)
|
loofah (2.21.3)
|
||||||
crass (~> 1.0.2)
|
crass (~> 1.0.2)
|
||||||
nokogiri (>= 1.12.0)
|
nokogiri (>= 1.12.0)
|
||||||
mail (2.8.1)
|
mail (2.8.1)
|
||||||
@@ -616,7 +616,7 @@ GEM
|
|||||||
uber (< 0.2.0)
|
uber (< 0.2.0)
|
||||||
request_store (1.5.1)
|
request_store (1.5.1)
|
||||||
rack (>= 1.4)
|
rack (>= 1.4)
|
||||||
responders (3.1.1)
|
responders (3.1.0)
|
||||||
actionpack (>= 5.2)
|
actionpack (>= 5.2)
|
||||||
railties (>= 5.2)
|
railties (>= 5.2)
|
||||||
rest-client (2.1.0)
|
rest-client (2.1.0)
|
||||||
@@ -702,18 +702,18 @@ GEM
|
|||||||
activesupport (>= 4)
|
activesupport (>= 4)
|
||||||
selectize-rails (0.12.6)
|
selectize-rails (0.12.6)
|
||||||
semantic_range (3.0.0)
|
semantic_range (3.0.0)
|
||||||
sentry-rails (5.12.0)
|
sentry-rails (5.11.0)
|
||||||
railties (>= 5.0)
|
railties (>= 5.0)
|
||||||
sentry-ruby (~> 5.12.0)
|
sentry-ruby (~> 5.11.0)
|
||||||
sentry-ruby (5.12.0)
|
sentry-ruby (5.11.0)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
sentry-sidekiq (5.12.0)
|
sentry-sidekiq (5.11.0)
|
||||||
sentry-ruby (~> 5.12.0)
|
sentry-ruby (~> 5.11.0)
|
||||||
sidekiq (>= 3.0)
|
sidekiq (>= 3.0)
|
||||||
sexp_processor (4.17.0)
|
sexp_processor (4.17.0)
|
||||||
shoulda-matchers (5.3.0)
|
shoulda-matchers (5.3.0)
|
||||||
activesupport (>= 5.2.0)
|
activesupport (>= 5.2.0)
|
||||||
sidekiq (7.1.6)
|
sidekiq (7.1.3)
|
||||||
concurrent-ruby (< 2)
|
concurrent-ruby (< 2)
|
||||||
connection_pool (>= 2.3.0)
|
connection_pool (>= 2.3.0)
|
||||||
rack (>= 2.2.4)
|
rack (>= 2.2.4)
|
||||||
@@ -732,12 +732,13 @@ GEM
|
|||||||
json (>= 1.8, < 3)
|
json (>= 1.8, < 3)
|
||||||
simplecov-html (~> 0.10.0)
|
simplecov-html (~> 0.10.0)
|
||||||
simplecov-html (0.10.2)
|
simplecov-html (0.10.2)
|
||||||
slack-ruby-client (2.2.0)
|
slack-ruby-client (2.0.0)
|
||||||
faraday (>= 2.0)
|
faraday (>= 2.0)
|
||||||
faraday-mashify
|
faraday-mashify
|
||||||
faraday-multipart
|
faraday-multipart
|
||||||
gli
|
gli
|
||||||
hashie
|
hashie
|
||||||
|
websocket-driver
|
||||||
snaky_hash (2.0.1)
|
snaky_hash (2.0.1)
|
||||||
hashie
|
hashie
|
||||||
version_gem (~> 1.1, >= 1.1.1)
|
version_gem (~> 1.1, >= 1.1.1)
|
||||||
@@ -816,7 +817,7 @@ GEM
|
|||||||
working_hours (1.4.1)
|
working_hours (1.4.1)
|
||||||
activesupport (>= 3.2)
|
activesupport (>= 3.2)
|
||||||
tzinfo
|
tzinfo
|
||||||
zeitwerk (2.6.12)
|
zeitwerk (2.6.11)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
arm64-darwin-20
|
arm64-darwin-20
|
||||||
@@ -855,7 +856,7 @@ DEPENDENCIES
|
|||||||
database_cleaner
|
database_cleaner
|
||||||
ddtrace
|
ddtrace
|
||||||
debug (~> 1.8)
|
debug (~> 1.8)
|
||||||
devise (>= 4.9.3)
|
devise
|
||||||
devise-secure_password!
|
devise-secure_password!
|
||||||
devise_token_auth
|
devise_token_auth
|
||||||
dotenv-rails
|
dotenv-rails
|
||||||
@@ -891,7 +892,7 @@ DEPENDENCIES
|
|||||||
line-bot-api
|
line-bot-api
|
||||||
liquid
|
liquid
|
||||||
listen
|
listen
|
||||||
lograge (~> 0.14.0)
|
lograge (~> 0.13.0)
|
||||||
maxminddb
|
maxminddb
|
||||||
mock_redis
|
mock_redis
|
||||||
neighbor
|
neighbor
|
||||||
@@ -915,7 +916,7 @@ DEPENDENCIES
|
|||||||
rails (~> 7.0.8.0)
|
rails (~> 7.0.8.0)
|
||||||
redis
|
redis
|
||||||
redis-namespace
|
redis-namespace
|
||||||
responders (>= 3.1.1)
|
responders
|
||||||
rest-client
|
rest-client
|
||||||
reverse_markdown
|
reverse_markdown
|
||||||
rspec-rails
|
rspec-rails
|
||||||
@@ -927,14 +928,14 @@ DEPENDENCIES
|
|||||||
scout_apm
|
scout_apm
|
||||||
scss_lint
|
scss_lint
|
||||||
seed_dump
|
seed_dump
|
||||||
sentry-rails (>= 5.12.0)
|
sentry-rails (>= 5.11.0)
|
||||||
sentry-ruby
|
sentry-ruby
|
||||||
sentry-sidekiq (>= 5.12.0)
|
sentry-sidekiq (>= 5.11.0)
|
||||||
shoulda-matchers
|
shoulda-matchers
|
||||||
sidekiq (>= 7.1.3)
|
sidekiq (>= 7.1.3)
|
||||||
sidekiq-cron (>= 1.10.1)
|
sidekiq-cron (>= 1.10.1)
|
||||||
simplecov (= 0.17.1)
|
simplecov (= 0.17.1)
|
||||||
slack-ruby-client (~> 2.2.0)
|
slack-ruby-client (~> 2.0.0)
|
||||||
spring
|
spring
|
||||||
spring-watcher-listen
|
spring-watcher-listen
|
||||||
squasher
|
squasher
|
||||||
|
|||||||
+1
-1
@@ -1 +1 @@
|
|||||||
2.6.0
|
2.4.0
|
||||||
|
|||||||
@@ -93,9 +93,6 @@ class Messages::Facebook::MessageBuilder < Messages::Messenger::MessageBuilder
|
|||||||
message_type: @message_type,
|
message_type: @message_type,
|
||||||
content: response.content,
|
content: response.content,
|
||||||
source_id: response.identifier,
|
source_id: response.identifier,
|
||||||
content_attributes: {
|
|
||||||
in_reply_to_external_id: response.in_reply_to_external_id
|
|
||||||
},
|
|
||||||
sender: @outgoing_echo ? nil : @contact_inbox.contact
|
sender: @outgoing_echo ? nil : @contact_inbox.contact
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -15,10 +15,7 @@ class V2::ReportBuilder
|
|||||||
end
|
end
|
||||||
|
|
||||||
def timeseries
|
def timeseries
|
||||||
return send(params[:metric]) if metric_valid?
|
send(params[:metric])
|
||||||
|
|
||||||
Rails.logger.error "ReportBuilder: Invalid metric - #{params[:metric]}"
|
|
||||||
{}
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# For backward compatible with old report
|
# For backward compatible with old report
|
||||||
@@ -56,16 +53,6 @@ class V2::ReportBuilder
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def metric_valid?
|
|
||||||
%w[conversations_count
|
|
||||||
incoming_messages_count
|
|
||||||
outgoing_messages_count
|
|
||||||
avg_first_response_time
|
|
||||||
avg_resolution_time reply_time
|
|
||||||
resolutions_count
|
|
||||||
reply_time].include?(params[:metric])
|
|
||||||
end
|
|
||||||
|
|
||||||
def inbox
|
def inbox
|
||||||
@inbox ||= account.inboxes.find(params[:id])
|
@inbox ||= account.inboxes.find(params[:id])
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ class Api::V1::Accounts::Actions::ContactMergesController < Api::V1::Accounts::B
|
|||||||
mergee_contact: @mergee_contact
|
mergee_contact: @mergee_contact
|
||||||
)
|
)
|
||||||
contact_merge_action.perform
|
contact_merge_action.perform
|
||||||
|
render json: @base_contact
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
|
|||||||
end
|
end
|
||||||
|
|
||||||
def toggle_status
|
def toggle_status
|
||||||
if params[:status].present?
|
if params[:status]
|
||||||
set_conversation_status
|
set_conversation_status
|
||||||
@status = @conversation.save!
|
@status = @conversation.save!
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
||||||
include Events::Types
|
include Events::Types
|
||||||
before_action :render_not_found_if_empty, only: [:toggle_typing, :toggle_status, :set_custom_attributes, :destroy_custom_attributes]
|
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@conversation = conversation
|
@conversation = conversation
|
||||||
@@ -28,7 +27,6 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
|||||||
|
|
||||||
conversation.contact_last_seen_at = DateTime.now.utc
|
conversation.contact_last_seen_at = DateTime.now.utc
|
||||||
conversation.save!
|
conversation.save!
|
||||||
::Conversations::MarkMessagesAsReadJob.perform_later(conversation)
|
|
||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -43,6 +41,8 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def toggle_typing
|
def toggle_typing
|
||||||
|
head :ok && return if conversation.nil?
|
||||||
|
|
||||||
case permitted_params[:typing_status]
|
case permitted_params[:typing_status]
|
||||||
when 'on'
|
when 'on'
|
||||||
trigger_typing_event(CONVERSATION_TYPING_ON)
|
trigger_typing_event(CONVERSATION_TYPING_ON)
|
||||||
@@ -54,6 +54,8 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def toggle_status
|
def toggle_status
|
||||||
|
return head :not_found if conversation.nil?
|
||||||
|
|
||||||
return head :forbidden unless @web_widget.end_conversation?
|
return head :forbidden unless @web_widget.end_conversation?
|
||||||
|
|
||||||
unless conversation.resolved?
|
unless conversation.resolved?
|
||||||
@@ -79,10 +81,6 @@ class Api::V1::Widget::ConversationsController < Api::V1::Widget::BaseController
|
|||||||
Rails.configuration.dispatcher.dispatch(event, Time.zone.now, conversation: conversation, user: @contact)
|
Rails.configuration.dispatcher.dispatch(event, Time.zone.now, conversation: conversation, user: @contact)
|
||||||
end
|
end
|
||||||
|
|
||||||
def render_not_found_if_empty
|
|
||||||
return head :not_found if conversation.nil?
|
|
||||||
end
|
|
||||||
|
|
||||||
def permitted_params
|
def permitted_params
|
||||||
params.permit(:id, :typing_status, :website_token, :email, contact: [:name, :email, :phone_number],
|
params.permit(:id, :typing_status, :website_token, :email, contact: [:name, :email, :phone_number],
|
||||||
message: [:content, :referer_url, :timestamp, :echo_id],
|
message: [:content, :referer_url, :timestamp, :echo_id],
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ class Public::Api::V1::Inboxes::ConversationsController < Public::Api::V1::Inbox
|
|||||||
def update_last_seen
|
def update_last_seen
|
||||||
@conversation.contact_last_seen_at = DateTime.now.utc
|
@conversation.contact_last_seen_at = DateTime.now.utc
|
||||||
@conversation.save!
|
@conversation.save!
|
||||||
::Conversations::MarkMessagesAsReadJob.perform_later(@conversation)
|
|
||||||
head :ok
|
head :ok
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +0,0 @@
|
|||||||
class Twilio::DeliveryStatusController < ApplicationController
|
|
||||||
def create
|
|
||||||
::Twilio::DeliveryStatusService.new(params: permitted_params).perform
|
|
||||||
|
|
||||||
head :no_content
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def permitted_params
|
|
||||||
params.permit(
|
|
||||||
:AccountSid,
|
|
||||||
:From,
|
|
||||||
:MessageSid,
|
|
||||||
:MessagingServiceSid,
|
|
||||||
:MessageStatus,
|
|
||||||
:ErrorCode,
|
|
||||||
:ErrorMessage
|
|
||||||
)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
v-if="!authUIFlags.isFetching && !accountUIFlags.isFetchingItem"
|
v-if="!authUIFlags.isFetching"
|
||||||
id="app"
|
id="app"
|
||||||
class="app-wrapper h-full flex-grow-0 min-h-0 w-full"
|
class="app-wrapper h-full flex-grow-0 min-h-0 w-full"
|
||||||
:class="{ 'app-rtl--wrapper': isRTLView }"
|
:class="{ 'app-rtl--wrapper': isRTLView }"
|
||||||
:dir="isRTLView ? 'rtl' : 'ltr'"
|
:dir="isRTLView ? 'rtl' : 'ltr'"
|
||||||
>
|
>
|
||||||
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
|
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
|
||||||
<template v-if="currentAccountId">
|
<template v-if="!accountUIFlags.isFetchingItem && currentAccountId">
|
||||||
<payment-pending-banner />
|
<payment-pending-banner />
|
||||||
<upgrade-banner />
|
<upgrade-banner />
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -84,11 +84,6 @@ export default {
|
|||||||
return axios.delete(endPoints('deleteAvatar').url);
|
return axios.delete(endPoints('deleteAvatar').url);
|
||||||
},
|
},
|
||||||
|
|
||||||
resetPassword({ email }) {
|
|
||||||
const urlData = endPoints('resetPassword');
|
|
||||||
return axios.post(urlData.url, { email });
|
|
||||||
},
|
|
||||||
|
|
||||||
setActiveAccount({ accountId }) {
|
setActiveAccount({ accountId }) {
|
||||||
const urlData = endPoints('setActiveAccount');
|
const urlData = endPoints('setActiveAccount');
|
||||||
return axios.put(urlData.url, {
|
return axios.put(urlData.url, {
|
||||||
|
|||||||
@@ -34,26 +34,26 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.hollow {
|
&.hollow {
|
||||||
@apply border border-woot-500 dark:border-woot-500 text-woot-500 dark:text-woot-500 hover:bg-woot-50 dark:hover:bg-woot-900;
|
@apply border border-slate-200 dark:border-slate-600 text-woot-700 dark:text-woot-100 hover:bg-woot-50 dark:hover:bg-woot-900;
|
||||||
|
|
||||||
&.secondary {
|
&.secondary {
|
||||||
@apply text-slate-700 border-slate-200 dark:border-slate-600 dark:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-700;
|
@apply text-slate-700 border-slate-200 dark:border-slate-600 dark:text-slate-100 hover:bg-slate-50 dark:hover:bg-slate-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.success {
|
&.success {
|
||||||
@apply text-green-700 dark:text-green-400 border-green-100 dark:border-green-600 hover:bg-green-50 dark:hover:bg-green-800;
|
@apply text-green-700 dark:text-green-100 hover:bg-green-50 dark:hover:bg-green-800;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.alert {
|
&.alert {
|
||||||
@apply text-red-700 dark:text-red-400 border-red-100 dark:border-red-600 hover:bg-red-50 dark:hover:bg-red-800;
|
@apply text-red-700 dark:text-red-100 hover:bg-red-50 dark:hover:bg-red-800;
|
||||||
}
|
}
|
||||||
|
|
||||||
&.warning {
|
&.warning {
|
||||||
@apply text-yellow-600 dark:text-yellow-600 border-yellow-600 dark:border-yellow-700 hover:bg-yellow-50 dark:hover:bg-yellow-800;
|
@apply text-yellow-700 dark:text-yellow-100 hover:bg-yellow-50 dark:hover:bg-yellow-800;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
@apply bg-woot-75 dark:bg-woot-800 border-slate-100 dark:border-woot-600 dark:text-woot-400;
|
@apply bg-slate-75 dark:bg-slate-900 border-slate-100 dark:border-slate-700;
|
||||||
|
|
||||||
&.secondary {
|
&.secondary {
|
||||||
@apply border-slate-100 dark:border-slate-700 text-slate-800 dark:text-slate-100;
|
@apply border-slate-100 dark:border-slate-700 text-slate-800 dark:text-slate-100;
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.warning {
|
&.warning {
|
||||||
@apply border-slate-100 dark:border-slate-700 text-yellow-700 dark:text-yellow-500;
|
@apply border-slate-100 dark:border-slate-700 text-yellow-700 dark:text-yellow-700;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,10 +152,6 @@
|
|||||||
|
|
||||||
&.is-image {
|
&.is-image {
|
||||||
@apply rounded-lg;
|
@apply rounded-lg;
|
||||||
|
|
||||||
.message__mail-head {
|
|
||||||
@apply px-4 py-2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
@apply flex items-center justify-center bg-modal dark:bg-modal z-[9990] h-full left-0 fixed top-0 w-full;
|
@apply flex items-center justify-center bg-modal dark:bg-modal z-[9990] h-full left-0 fixed top-0 w-full;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal--close {
|
||||||
|
@apply absolute right-2 rtl:right-[unset] rtl:left-2 top-2;
|
||||||
|
}
|
||||||
|
|
||||||
.page-top-bar {
|
.page-top-bar {
|
||||||
@apply px-8 pt-9 pb-0;
|
@apply px-8 pt-9 pb-0;
|
||||||
|
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
transition: box-shadow 0.35s $swift-ease-out-function,
|
transition: box-shadow 0.35s $swift-ease-out-function,
|
||||||
height 2s $swift-ease-out-function;
|
height 2s $swift-ease-out-function;
|
||||||
|
|
||||||
&.is-focused {
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
}
|
|
||||||
|
|
||||||
.reply-box__top {
|
.reply-box__top {
|
||||||
.icon {
|
.icon {
|
||||||
color: $medium-gray;
|
color: $medium-gray;
|
||||||
@@ -47,8 +43,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.is-private {
|
&.is-private {
|
||||||
@apply bg-yellow-100 dark:bg-yellow-800;
|
|
||||||
|
|
||||||
.reply-box__top {
|
.reply-box__top {
|
||||||
@apply bg-yellow-100 dark:bg-yellow-800;
|
@apply bg-yellow-100 dark:bg-yellow-800;
|
||||||
|
|
||||||
|
|||||||
@@ -4,19 +4,15 @@
|
|||||||
v-if="show"
|
v-if="show"
|
||||||
:class="modalClassName"
|
:class="modalClassName"
|
||||||
transition="modal"
|
transition="modal"
|
||||||
@mousedown="handleMouseDown"
|
@click="onBackDropClick"
|
||||||
>
|
>
|
||||||
<div
|
<div :class="modalContainerClassName" @click.stop>
|
||||||
:class="modalContainerClassName"
|
|
||||||
@mouse.stop
|
|
||||||
@mousedown="event => event.stopPropagation()"
|
|
||||||
>
|
|
||||||
<woot-button
|
<woot-button
|
||||||
v-if="showCloseButton"
|
v-if="showCloseButton"
|
||||||
color-scheme="secondary"
|
color-scheme="secondary"
|
||||||
icon="dismiss"
|
icon="dismiss"
|
||||||
variant="clear"
|
variant="clear"
|
||||||
class="absolute ltr:right-2 rtl:left-2 top-2 z-10"
|
class="modal--close"
|
||||||
@click="close"
|
@click="close"
|
||||||
/>
|
/>
|
||||||
<slot />
|
<slot />
|
||||||
@@ -54,11 +50,6 @@ export default {
|
|||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
mousedDownOnBackdrop: false,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
modalContainerClassName() {
|
modalContainerClassName() {
|
||||||
let className =
|
let className =
|
||||||
@@ -86,22 +77,13 @@ export default {
|
|||||||
this.onClose();
|
this.onClose();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.body.addEventListener('mouseup', this.onMouseUp);
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
document.body.removeEventListener('mouseup', this.onMouseUp);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleMouseDown() {
|
|
||||||
this.mousedDownOnBackdrop = true;
|
|
||||||
},
|
|
||||||
close() {
|
close() {
|
||||||
this.onClose();
|
this.onClose();
|
||||||
},
|
},
|
||||||
onMouseUp() {
|
onBackDropClick() {
|
||||||
if (this.mousedDownOnBackdrop) {
|
if (this.closeOnBackdropClick) {
|
||||||
this.mousedDownOnBackdrop = false;
|
|
||||||
this.onClose();
|
this.onClose();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
@key-shortcut-modal="toggleKeyShortcutModal"
|
@key-shortcut-modal="toggleKeyShortcutModal"
|
||||||
@open-notification-panel="openNotificationPanel"
|
@open-notification-panel="openNotificationPanel"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<secondary-sidebar
|
<secondary-sidebar
|
||||||
v-if="showSecondarySidebar"
|
v-if="showSecondarySidebar"
|
||||||
:class="sidebarClassName"
|
:class="sidebarClassName"
|
||||||
@@ -113,12 +114,7 @@ export default {
|
|||||||
if (!isAvailableForTheUser) {
|
if (!isAvailableForTheUser) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (
|
|
||||||
menuItem.alwaysVisibleOnChatwootInstances &&
|
|
||||||
!this.isACustomBrandedInstance
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (menuItem.featureFlag) {
|
if (menuItem.featureFlag) {
|
||||||
return this.isFeatureEnabledonAccount(
|
return this.isFeatureEnabledonAccount(
|
||||||
this.accountId,
|
this.accountId,
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ const primaryMenuItems = accountId => [
|
|||||||
key: 'helpcenter',
|
key: 'helpcenter',
|
||||||
label: 'HELP_CENTER.TITLE',
|
label: 'HELP_CENTER.TITLE',
|
||||||
featureFlag: FEATURE_FLAGS.HELP_CENTER,
|
featureFlag: FEATURE_FLAGS.HELP_CENTER,
|
||||||
alwaysVisibleOnChatwootInstances: true,
|
|
||||||
toState: frontendURL(`accounts/${accountId}/portals`),
|
toState: frontendURL(`accounts/${accountId}/portals`),
|
||||||
toStateName: 'default_portal_articles',
|
toStateName: 'default_portal_articles',
|
||||||
roles: ['administrator'],
|
roles: ['administrator'],
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="preview-item__wrap flex overflow-auto max-h-[12.5rem]">
|
<div
|
||||||
|
class="preview-item__wrap flex flex-col overflow-auto mt-4 max-h-[12.5rem]"
|
||||||
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(attachment, index) in attachments"
|
v-for="(attachment, index) in attachments"
|
||||||
:key="attachment.id"
|
:key="attachment.id"
|
||||||
class="preview-item flex items-center p-1 bg-slate-50 dark:bg-slate-800 gap-1 rounded-md w-[15rem] mb-1"
|
class="preview-item flex p-1 bg-slate-50 dark:bg-slate-800 rounded-md w-[15rem] mb-1"
|
||||||
>
|
>
|
||||||
<div class="max-w-[4rem] flex-shrink-0 w-6 flex items-center">
|
<div class="max-w-[4rem] flex-shrink-0 w-6 flex items-center">
|
||||||
<img
|
<img
|
||||||
@@ -15,7 +17,7 @@
|
|||||||
📄
|
📄
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="max-w-[60%] min-w-[50%] overflow-hidden text-ellipsis">
|
<div class="max-w-[60%] min-w-[50%] overflow-hidden text-ellipsis ml-2">
|
||||||
<span
|
<span
|
||||||
class="h-4 overflow-hidden text-sm font-medium text-ellipsis whitespace-nowrap"
|
class="h-4 overflow-hidden text-sm font-medium text-ellipsis whitespace-nowrap"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
/>
|
/>
|
||||||
<iframe
|
<iframe
|
||||||
v-if="configItem.type === 'frame' && configItem.url"
|
v-if="configItem.type === 'frame' && configItem.url"
|
||||||
:id="getFrameId(index)"
|
:id="`dashboard-app--frame-${index}`"
|
||||||
:src="configItem.url"
|
:src="configItem.url"
|
||||||
@load="() => onIframeLoad(index)"
|
@load="() => onIframeLoad(index)"
|
||||||
/>
|
/>
|
||||||
@@ -39,10 +39,6 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
position: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -86,14 +82,10 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getFrameId(index) {
|
|
||||||
return `dashboard-app--frame-${this.position}-${index}`;
|
|
||||||
},
|
|
||||||
onIframeLoad(index) {
|
onIframeLoad(index) {
|
||||||
// A possible alternative is to use ref instead of document.getElementById
|
const frameElement = document.getElementById(
|
||||||
// However, when ref is used together with v-for, the ref you get will be
|
`dashboard-app--frame-${index}`
|
||||||
// an array containing the child components mirroring the data source.
|
);
|
||||||
const frameElement = document.getElementById(this.getFrameId(index));
|
|
||||||
const eventData = { event: 'appContext', data: this.dashboardAppContext };
|
const eventData = { event: 'appContext', data: this.dashboardAppContext };
|
||||||
frameElement.contentWindow.postMessage(JSON.stringify(eventData), '*');
|
frameElement.contentWindow.postMessage(JSON.stringify(eventData), '*');
|
||||||
this.iframeLoading = false;
|
this.iframeLoading = false;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div ref="editorRoot" class="relative editor-root">
|
<div class="editor-root">
|
||||||
<tag-agents
|
<tag-agents
|
||||||
v-if="showUserMentions && isPrivate"
|
v-if="showUserMentions && isPrivate"
|
||||||
:search-key="mentionSearchKey"
|
:search-key="mentionSearchKey"
|
||||||
@@ -23,23 +23,6 @@
|
|||||||
@change="onFileChange"
|
@change="onFileChange"
|
||||||
/>
|
/>
|
||||||
<div ref="editor" />
|
<div ref="editor" />
|
||||||
<div
|
|
||||||
v-show="isImageNodeSelected && showImageResizeToolbar"
|
|
||||||
class="absolute shadow-md rounded-[4px] flex gap-1 py-1 px-1 bg-slate-50 dark:bg-slate-700 text-slate-800 dark:text-slate-50"
|
|
||||||
:style="{
|
|
||||||
top: toolbarPosition.top,
|
|
||||||
left: toolbarPosition.left,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<button
|
|
||||||
v-for="size in sizes"
|
|
||||||
:key="size.name"
|
|
||||||
class="text-xs font-medium rounded-[4px] border border-solid border-slate-200 dark:border-slate-600 px-1.5 py-0.5 hover:bg-slate-100 dark:hover:bg-slate-800"
|
|
||||||
@click="setURLWithQueryAndImageSize(size)"
|
|
||||||
>
|
|
||||||
{{ size.name }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<slot name="footer" />
|
<slot name="footer" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -58,7 +41,6 @@ import {
|
|||||||
suggestionsPlugin,
|
suggestionsPlugin,
|
||||||
triggerCharacters,
|
triggerCharacters,
|
||||||
} from '@chatwoot/prosemirror-schema/src/mentions/plugin';
|
} from '@chatwoot/prosemirror-schema/src/mentions/plugin';
|
||||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
|
||||||
|
|
||||||
import TagAgents from '../conversation/TagAgents.vue';
|
import TagAgents from '../conversation/TagAgents.vue';
|
||||||
import CannedResponse from '../conversation/CannedResponse.vue';
|
import CannedResponse from '../conversation/CannedResponse.vue';
|
||||||
@@ -66,10 +48,6 @@ import VariableList from '../conversation/VariableList.vue';
|
|||||||
import {
|
import {
|
||||||
appendSignature,
|
appendSignature,
|
||||||
removeSignature,
|
removeSignature,
|
||||||
insertAtCursor,
|
|
||||||
scrollCursorIntoView,
|
|
||||||
findNodeToInsertImage,
|
|
||||||
setURLWithQueryAndSize,
|
|
||||||
} from 'dashboard/helper/editorHelper';
|
} from 'dashboard/helper/editorHelper';
|
||||||
|
|
||||||
const TYPING_INDICATOR_IDLE_TIME = 4000;
|
const TYPING_INDICATOR_IDLE_TIME = 4000;
|
||||||
@@ -89,10 +67,8 @@ import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events';
|
|||||||
import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
|
import { checkFileSizeLimit } from 'shared/helpers/FileHelper';
|
||||||
import { uploadFile } from 'dashboard/helper/uploadHelper';
|
import { uploadFile } from 'dashboard/helper/uploadHelper';
|
||||||
import alertMixin from 'shared/mixins/alertMixin';
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
import {
|
import { findNodeToInsertImage } from 'dashboard/helper/messageEditorHelper';
|
||||||
MESSAGE_EDITOR_MENU_OPTIONS,
|
import { MESSAGE_EDITOR_MENU_OPTIONS } from 'dashboard/constants/editor';
|
||||||
MESSAGE_EDITOR_IMAGE_RESIZES,
|
|
||||||
} from 'dashboard/constants/editor';
|
|
||||||
|
|
||||||
const createState = (
|
const createState = (
|
||||||
content,
|
content,
|
||||||
@@ -135,7 +111,6 @@ export default {
|
|||||||
// allowSignature is a kill switch, ensuring no signature methods
|
// allowSignature is a kill switch, ensuring no signature methods
|
||||||
// are triggered except when this flag is true
|
// are triggered except when this flag is true
|
||||||
allowSignature: { type: Boolean, default: false },
|
allowSignature: { type: Boolean, default: false },
|
||||||
showImageResizeToolbar: { type: Boolean, default: false }, // A kill switch to show or hide the image toolbar
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -148,16 +123,9 @@ export default {
|
|||||||
editorView: null,
|
editorView: null,
|
||||||
range: null,
|
range: null,
|
||||||
state: undefined,
|
state: undefined,
|
||||||
isImageNodeSelected: false,
|
|
||||||
toolbarPosition: { top: 0, left: 0 },
|
|
||||||
sizes: MESSAGE_EDITOR_IMAGE_RESIZES,
|
|
||||||
selectedImageNode: null,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
editorRoot() {
|
|
||||||
return this.$refs.editorRoot;
|
|
||||||
},
|
|
||||||
contentFromEditor() {
|
contentFromEditor() {
|
||||||
return MessageMarkdownSerializer.serialize(this.editorView.state.doc);
|
return MessageMarkdownSerializer.serialize(this.editorView.state.doc);
|
||||||
},
|
},
|
||||||
@@ -305,7 +273,6 @@ export default {
|
|||||||
const tr = this.editorView.state.tr.replaceSelectionWith(node);
|
const tr = this.editorView.state.tr.replaceSelectionWith(node);
|
||||||
this.editorView.focus();
|
this.editorView.focus();
|
||||||
this.state = this.editorView.state.apply(tr);
|
this.state = this.editorView.state.apply(tr);
|
||||||
this.editorView.updateState(this.state);
|
|
||||||
this.emitOnChange();
|
this.emitOnChange();
|
||||||
this.$emit('clear-selection');
|
this.$emit('clear-selection');
|
||||||
}
|
}
|
||||||
@@ -331,17 +298,7 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
this.createEditorView();
|
this.createEditorView();
|
||||||
this.editorView.updateState(this.state);
|
this.editorView.updateState(this.state);
|
||||||
this.focusEditorInputField();
|
this.focusEditor(this.value);
|
||||||
|
|
||||||
// BUS Event to insert text or markdown into the editor at the
|
|
||||||
// current cursor position.
|
|
||||||
// Components using this
|
|
||||||
// 1. SearchPopover.vue
|
|
||||||
|
|
||||||
bus.$on(BUS_EVENTS.INSERT_INTO_RICH_EDITOR, this.insertContentIntoEditor);
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
bus.$off(BUS_EVENTS.INSERT_INTO_RICH_EDITOR, this.insertContentIntoEditor);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
reloadState(content = this.value) {
|
reloadState(content = this.value) {
|
||||||
@@ -428,7 +385,6 @@ export default {
|
|||||||
state: this.state,
|
state: this.state,
|
||||||
dispatchTransaction: tx => {
|
dispatchTransaction: tx => {
|
||||||
this.state = this.state.apply(tx);
|
this.state = this.state.apply(tx);
|
||||||
this.editorView.updateState(this.state);
|
|
||||||
this.emitOnChange();
|
this.emitOnChange();
|
||||||
},
|
},
|
||||||
handleDOMEvents: {
|
handleDOMEvents: {
|
||||||
@@ -441,9 +397,6 @@ export default {
|
|||||||
focus: () => {
|
focus: () => {
|
||||||
this.onFocus();
|
this.onFocus();
|
||||||
},
|
},
|
||||||
click: () => {
|
|
||||||
// this.isEditorMouseFocusedOnAnImage(); Enable it when the backend supports for message resize is done.
|
|
||||||
},
|
|
||||||
blur: () => {
|
blur: () => {
|
||||||
this.onBlur();
|
this.onBlur();
|
||||||
},
|
},
|
||||||
@@ -456,50 +409,6 @@ export default {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
isEditorMouseFocusedOnAnImage() {
|
|
||||||
if (!this.showImageResizeToolbar) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.selectedImageNode = document.querySelector(
|
|
||||||
'img.ProseMirror-selectednode'
|
|
||||||
);
|
|
||||||
if (this.selectedImageNode) {
|
|
||||||
this.isImageNodeSelected = !!this.selectedImageNode;
|
|
||||||
// Get the position of the selected node
|
|
||||||
this.setToolbarPosition();
|
|
||||||
} else {
|
|
||||||
this.isImageNodeSelected = false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setToolbarPosition() {
|
|
||||||
const editorRect = this.editorRoot.getBoundingClientRect();
|
|
||||||
const rect = this.selectedImageNode.getBoundingClientRect();
|
|
||||||
this.toolbarPosition = {
|
|
||||||
top: `${rect.top - editorRect.top - 30}px`,
|
|
||||||
left: `${rect.left - editorRect.left - 4}px`,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
setURLWithQueryAndImageSize(size) {
|
|
||||||
if (!this.showImageResizeToolbar) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setURLWithQueryAndSize(this.selectedImageNode, size, this.editorView);
|
|
||||||
this.isImageNodeSelected = false;
|
|
||||||
},
|
|
||||||
updateImgToolbarOnDelete() {
|
|
||||||
// check if the selected node is present or not on keyup
|
|
||||||
// this is needed because the user can select an image and then delete it
|
|
||||||
// in that case, the selected node will be null and we need to hide the toolbar
|
|
||||||
// otherwise, the toolbar will be visible even when the image is deleted and cause some errors
|
|
||||||
if (this.selectedImageNode) {
|
|
||||||
const hasImgSelectedNode = document.querySelector(
|
|
||||||
'img.ProseMirror-selectednode'
|
|
||||||
);
|
|
||||||
if (!hasImgSelectedNode) {
|
|
||||||
this.isImageNodeSelected = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
isEnterToSendEnabled() {
|
isEnterToSendEnabled() {
|
||||||
return isEditorHotKeyEnabled(this.uiSettings, 'enter');
|
return isEditorHotKeyEnabled(this.uiSettings, 'enter');
|
||||||
},
|
},
|
||||||
@@ -532,7 +441,11 @@ export default {
|
|||||||
userFullName: mentionItem.name,
|
userFullName: mentionItem.name,
|
||||||
});
|
});
|
||||||
|
|
||||||
this.insertNodeIntoEditor(node, this.range.from, this.range.to);
|
const tr = this.editorView.state.tr
|
||||||
|
.replaceWith(this.range.from, this.range.to, node)
|
||||||
|
.insertText(` `);
|
||||||
|
this.state = this.editorView.state.apply(tr);
|
||||||
|
this.emitOnChange();
|
||||||
this.$track(CONVERSATION_EVENTS.USED_MENTIONS);
|
this.$track(CONVERSATION_EVENTS.USED_MENTIONS);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@@ -542,22 +455,30 @@ export default {
|
|||||||
message: cannedItem,
|
message: cannedItem,
|
||||||
variables: this.variables,
|
variables: this.variables,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this.editorView) {
|
if (!this.editorView) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let from = this.range.from - 1;
|
||||||
let node = new MessageMarkdownTransformer(messageSchema).parse(
|
let node = new MessageMarkdownTransformer(messageSchema).parse(
|
||||||
updatedMessage
|
updatedMessage
|
||||||
);
|
);
|
||||||
|
|
||||||
const from =
|
if (node.textContent === updatedMessage) {
|
||||||
node.textContent === updatedMessage
|
node = this.editorView.state.schema.text(updatedMessage);
|
||||||
? this.range.from
|
from = this.range.from;
|
||||||
: this.range.from - 1;
|
}
|
||||||
|
|
||||||
this.insertNodeIntoEditor(node, from, this.range.to);
|
const tr = this.editorView.state.tr.replaceWith(
|
||||||
|
from,
|
||||||
|
this.range.to,
|
||||||
|
node
|
||||||
|
);
|
||||||
|
|
||||||
|
this.state = this.editorView.state.apply(tr);
|
||||||
|
this.emitOnChange();
|
||||||
|
|
||||||
|
tr.scrollIntoView();
|
||||||
this.$track(CONVERSATION_EVENTS.INSERTED_A_CANNED_RESPONSE);
|
this.$track(CONVERSATION_EVENTS.INSERTED_A_CANNED_RESPONSE);
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
@@ -565,14 +486,23 @@ export default {
|
|||||||
if (!this.editorView) {
|
if (!this.editorView) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
let node = this.editorView.state.schema.text(`{{${variable}}}`);
|
||||||
|
const from = this.range.from;
|
||||||
|
|
||||||
const content = `{{${variable}}}`;
|
const tr = this.editorView.state.tr.replaceWith(
|
||||||
let node = this.editorView.state.schema.text(content);
|
from,
|
||||||
const { from, to } = this.range;
|
this.range.to,
|
||||||
|
node
|
||||||
|
);
|
||||||
|
|
||||||
this.insertNodeIntoEditor(node, from, to);
|
this.state = this.editorView.state.apply(tr);
|
||||||
|
this.emitOnChange();
|
||||||
|
|
||||||
|
// The `{{ }}` are added to the message, but the cursor is placed
|
||||||
|
// and onExit of suggestionsPlugin is not called. So we need to manually hide
|
||||||
this.showVariables = false;
|
this.showVariables = false;
|
||||||
this.$track(CONVERSATION_EVENTS.INSERTED_A_VARIABLE);
|
this.$track(CONVERSATION_EVENTS.INSERTED_A_VARIABLE);
|
||||||
|
tr.scrollIntoView();
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
openFileBrowser() {
|
openFileBrowser() {
|
||||||
@@ -628,6 +558,8 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
emitOnChange() {
|
emitOnChange() {
|
||||||
|
this.editorView.updateState(this.state);
|
||||||
|
|
||||||
this.$emit('input', this.contentFromEditor);
|
this.$emit('input', this.contentFromEditor);
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -670,7 +602,6 @@ export default {
|
|||||||
() => this.resetTyping(),
|
() => this.resetTyping(),
|
||||||
TYPING_INDICATOR_IDLE_TIME
|
TYPING_INDICATOR_IDLE_TIME
|
||||||
);
|
);
|
||||||
// this.updateImgToolbarOnDelete(); Enable it when the backend supports for message resize is done.
|
|
||||||
},
|
},
|
||||||
onKeydown(event) {
|
onKeydown(event) {
|
||||||
if (this.isEnterToSendEnabled()) {
|
if (this.isEnterToSendEnabled()) {
|
||||||
@@ -688,19 +619,6 @@ export default {
|
|||||||
onFocus() {
|
onFocus() {
|
||||||
this.$emit('focus');
|
this.$emit('focus');
|
||||||
},
|
},
|
||||||
insertContentIntoEditor(content, defaultFrom = 0) {
|
|
||||||
const from = defaultFrom || this.editorView.state.selection.from || 0;
|
|
||||||
let node = new MessageMarkdownTransformer(messageSchema).parse(content);
|
|
||||||
|
|
||||||
this.insertNodeIntoEditor(node, from, undefined);
|
|
||||||
},
|
|
||||||
insertNodeIntoEditor(node, from = 0, to = 0) {
|
|
||||||
this.state = insertAtCursor(this.editorView, node, from, to);
|
|
||||||
this.emitOnChange();
|
|
||||||
this.$nextTick(() => {
|
|
||||||
scrollCursorIntoView(this.editorView);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -794,6 +712,6 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.editor-warning__message {
|
.editor-warning__message {
|
||||||
@apply text-red-400 dark:text-red-400 font-normal text-sm pt-1 pb-0 px-0;
|
@apply text-red-400 dark:text-red-400 text-sm font-normal pt-1 pb-0 px-0;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -14,11 +14,10 @@
|
|||||||
<file-upload
|
<file-upload
|
||||||
ref="upload"
|
ref="upload"
|
||||||
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
|
v-tooltip.top-end="$t('CONVERSATION.REPLYBOX.TIP_ATTACH_ICON')"
|
||||||
input-id="conversationAttachment"
|
|
||||||
:size="4096 * 4096"
|
:size="4096 * 4096"
|
||||||
:accept="allowedFileTypes"
|
:accept="allowedFileTypes"
|
||||||
:multiple="enableMultipleFileUpload"
|
:multiple="enableMultipleFileUpload"
|
||||||
:drop="enableDragAndDrop"
|
:drop="true"
|
||||||
:drop-directory="false"
|
:drop-directory="false"
|
||||||
:data="{
|
:data="{
|
||||||
direct_upload_url: '/rails/active_storage/direct_uploads',
|
direct_upload_url: '/rails/active_storage/direct_uploads',
|
||||||
@@ -101,9 +100,9 @@
|
|||||||
<transition name="modal-fade">
|
<transition name="modal-fade">
|
||||||
<div
|
<div
|
||||||
v-show="$refs.upload && $refs.upload.dropActive"
|
v-show="$refs.upload && $refs.upload.dropActive"
|
||||||
class="flex items-center justify-center gap-2 fixed left-0 right-0 top-0 bottom-0 w-full h-full z-20 text-slate-600 dark:text-slate-200 bg-white_transparent dark:bg-black_transparent flex-col"
|
class="modal-mask"
|
||||||
>
|
>
|
||||||
<fluent-icon icon="cloud-backup" size="40" />
|
<fluent-icon icon="cloud-backup" />
|
||||||
<h4 class="page-sub-title text-slate-600 dark:text-slate-200">
|
<h4 class="page-sub-title text-slate-600 dark:text-slate-200">
|
||||||
{{ $t('CONVERSATION.REPLYBOX.DRAG_DROP') }}
|
{{ $t('CONVERSATION.REPLYBOX.DRAG_DROP') }}
|
||||||
</h4>
|
</h4>
|
||||||
@@ -229,10 +228,6 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
newConversationModalActive: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
@@ -279,9 +274,6 @@ export default {
|
|||||||
}
|
}
|
||||||
return ALLOWED_FILE_TYPES;
|
return ALLOWED_FILE_TYPES;
|
||||||
},
|
},
|
||||||
enableDragAndDrop() {
|
|
||||||
return !this.newConversationModalActive;
|
|
||||||
},
|
|
||||||
audioRecorderPlayStopIcon() {
|
audioRecorderPlayStopIcon() {
|
||||||
switch (this.recordingAudioState) {
|
switch (this.recordingAudioState) {
|
||||||
// playing paused recording stopped inactive destroyed
|
// playing paused recording stopped inactive destroyed
|
||||||
@@ -354,4 +346,12 @@ export default {
|
|||||||
@apply dark:bg-slate-800 bg-slate-100;
|
@apply dark:bg-slate-800 bg-slate-100;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.modal-mask {
|
||||||
|
@apply text-slate-600 dark:text-slate-200 bg-white_transparent dark:bg-black_transparent flex-col;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
@apply text-[5rem];
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -52,7 +52,6 @@
|
|||||||
:key="currentChat.id + '-' + dashboardApp.id"
|
:key="currentChat.id + '-' + dashboardApp.id"
|
||||||
:is-visible="activeIndex - 1 === index"
|
:is-visible="activeIndex - 1 === index"
|
||||||
:config="dashboardApps[index].content"
|
:config="dashboardApps[index].content"
|
||||||
:position="index"
|
|
||||||
:current-chat="currentChat"
|
:current-chat="currentChat"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="relative flex items-start flex-grow-0 flex-shrink-0 w-auto max-w-full px-4 py-0 border-t-0 border-b-0 border-l-2 border-r-0 border-transparent border-solid cursor-pointer conversation hover:bg-slate-25 dark:hover:bg-slate-800 group"
|
class="conversation flex flex-shrink-0 flex-grow-0 w-auto max-w-full cursor-pointer relative py-0 px-4 border-transparent border-l-2 border-t-0 border-b-0 border-r-0 border-solid items-start hover:bg-slate-25 dark:hover:bg-slate-800 group"
|
||||||
:class="{
|
:class="{
|
||||||
'active bg-slate-25 dark:bg-slate-800 border-woot-500': isActiveChat,
|
'active bg-slate-25 dark:bg-slate-800 border-woot-500': isActiveChat,
|
||||||
'unread-chat': hasUnread,
|
'unread-chat': hasUnread,
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
size="40px"
|
size="40px"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
class="px-0 py-3 border-b group-last:border-transparent group-hover:border-transparent border-slate-50 dark:border-slate-800/75 columns"
|
class="py-3 px-0 border-b group-last:border-transparent group-hover:border-transparent border-slate-50 dark:border-slate-800/75 columns"
|
||||||
>
|
>
|
||||||
<div class="flex justify-between">
|
<div class="flex justify-between">
|
||||||
<inbox-name v-if="showInboxName" :inbox="inbox" />
|
<inbox-name v-if="showInboxName" :inbox="inbox" />
|
||||||
@@ -55,11 +55,44 @@
|
|||||||
>
|
>
|
||||||
{{ currentContact.name }}
|
{{ currentContact.name }}
|
||||||
</h4>
|
</h4>
|
||||||
<message-preview
|
<p
|
||||||
v-if="lastMessageInChat"
|
v-if="lastMessageInChat"
|
||||||
:message="lastMessageInChat"
|
class="conversation--message text-slate-700 dark:text-slate-200 text-sm my-0 mx-2 leading-6 h-6 max-w-[96%] w-[16.875rem] overflow-hidden text-ellipsis whitespace-nowrap"
|
||||||
class="conversation--message my-0 mx-2 leading-6 h-6 max-w-[96%] w-[16.875rem] text-sm text-slate-700 dark:text-slate-200"
|
>
|
||||||
/>
|
<fluent-icon
|
||||||
|
v-if="isMessagePrivate"
|
||||||
|
size="16"
|
||||||
|
class="-mt-0.5 align-middle text-slate-600 dark:text-slate-300 inline-block"
|
||||||
|
icon="lock-closed"
|
||||||
|
/>
|
||||||
|
<fluent-icon
|
||||||
|
v-else-if="messageByAgent"
|
||||||
|
size="16"
|
||||||
|
class="-mt-0.5 align-middle text-slate-600 dark:text-slate-300 inline-block"
|
||||||
|
icon="arrow-reply"
|
||||||
|
/>
|
||||||
|
<fluent-icon
|
||||||
|
v-else-if="isMessageAnActivity"
|
||||||
|
size="16"
|
||||||
|
class="-mt-0.5 align-middle text-slate-600 dark:text-slate-300 inline-block"
|
||||||
|
icon="info"
|
||||||
|
/>
|
||||||
|
<span v-if="lastMessageInChat.content">
|
||||||
|
{{ parsedLastMessage }}
|
||||||
|
</span>
|
||||||
|
<span v-else-if="lastMessageInChat.attachments">
|
||||||
|
<fluent-icon
|
||||||
|
v-if="attachmentIcon"
|
||||||
|
size="16"
|
||||||
|
class="-mt-0.5 align-middle inline-block text-slate-600 dark:text-slate-300"
|
||||||
|
:icon="attachmentIcon"
|
||||||
|
/>
|
||||||
|
{{ $t(`${attachmentMessageContent}`) }}
|
||||||
|
</span>
|
||||||
|
<span v-else>
|
||||||
|
{{ $t('CHAT_LIST.NO_CONTENT') }}
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
<p
|
<p
|
||||||
v-else
|
v-else
|
||||||
class="conversation--message text-slate-700 dark:text-slate-200 text-sm my-0 mx-2 leading-6 h-6 max-w-[96%] w-[16.875rem] overflow-hidden text-ellipsis whitespace-nowrap"
|
class="conversation--message text-slate-700 dark:text-slate-200 text-sm my-0 mx-2 leading-6 h-6 max-w-[96%] w-[16.875rem] overflow-hidden text-ellipsis whitespace-nowrap"
|
||||||
@@ -73,8 +106,8 @@
|
|||||||
{{ $t(`CHAT_LIST.NO_MESSAGES`) }}
|
{{ $t(`CHAT_LIST.NO_MESSAGES`) }}
|
||||||
</span>
|
</span>
|
||||||
</p>
|
</p>
|
||||||
<div class="absolute flex flex-col conversation--meta right-4 top-4">
|
<div class="conversation--meta flex flex-col absolute right-4 top-4">
|
||||||
<span class="ml-auto font-normal leading-4 text-black-600 text-xxs">
|
<span class="text-black-600 text-xxs font-normal leading-4 ml-auto">
|
||||||
<time-ago
|
<time-ago
|
||||||
:last-activity-timestamp="chat.timestamp"
|
:last-activity-timestamp="chat.timestamp"
|
||||||
:created-at-timestamp="chat.created_at"
|
:created-at-timestamp="chat.created_at"
|
||||||
@@ -112,8 +145,9 @@
|
|||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
||||||
|
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||||
import Thumbnail from '../Thumbnail.vue';
|
import Thumbnail from '../Thumbnail.vue';
|
||||||
import MessagePreview from './MessagePreview.vue';
|
|
||||||
import conversationMixin from '../../../mixins/conversations';
|
import conversationMixin from '../../../mixins/conversations';
|
||||||
import timeMixin from '../../../mixins/time';
|
import timeMixin from '../../../mixins/time';
|
||||||
import router from '../../../routes';
|
import router from '../../../routes';
|
||||||
@@ -125,6 +159,14 @@ import alertMixin from 'shared/mixins/alertMixin';
|
|||||||
import TimeAgo from 'dashboard/components/ui/TimeAgo.vue';
|
import TimeAgo from 'dashboard/components/ui/TimeAgo.vue';
|
||||||
import CardLabels from './conversationCardComponents/CardLabels.vue';
|
import CardLabels from './conversationCardComponents/CardLabels.vue';
|
||||||
import PriorityMark from './PriorityMark.vue';
|
import PriorityMark from './PriorityMark.vue';
|
||||||
|
const ATTACHMENT_ICONS = {
|
||||||
|
image: 'image',
|
||||||
|
audio: 'headphones-sound-wave',
|
||||||
|
video: 'video',
|
||||||
|
file: 'document',
|
||||||
|
location: 'location',
|
||||||
|
fallback: 'link',
|
||||||
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -133,11 +175,16 @@ export default {
|
|||||||
Thumbnail,
|
Thumbnail,
|
||||||
ConversationContextMenu,
|
ConversationContextMenu,
|
||||||
TimeAgo,
|
TimeAgo,
|
||||||
MessagePreview,
|
|
||||||
PriorityMark,
|
PriorityMark,
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [inboxMixin, timeMixin, conversationMixin, alertMixin],
|
mixins: [
|
||||||
|
inboxMixin,
|
||||||
|
timeMixin,
|
||||||
|
conversationMixin,
|
||||||
|
messageFormatterMixin,
|
||||||
|
alertMixin,
|
||||||
|
],
|
||||||
props: {
|
props: {
|
||||||
activeLabel: {
|
activeLabel: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -211,6 +258,20 @@ export default {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
lastMessageFileType() {
|
||||||
|
const lastMessage = this.lastMessageInChat;
|
||||||
|
const [{ file_type: fileType } = {}] = lastMessage.attachments;
|
||||||
|
return fileType;
|
||||||
|
},
|
||||||
|
|
||||||
|
attachmentIcon() {
|
||||||
|
return ATTACHMENT_ICONS[this.lastMessageFileType];
|
||||||
|
},
|
||||||
|
|
||||||
|
attachmentMessageContent() {
|
||||||
|
return `CHAT_LIST.ATTACHMENTS.${this.lastMessageFileType}.CONTENT`;
|
||||||
|
},
|
||||||
|
|
||||||
isActiveChat() {
|
isActiveChat() {
|
||||||
return this.currentChat.id === this.chat.id;
|
return this.currentChat.id === this.chat.id;
|
||||||
},
|
},
|
||||||
@@ -231,6 +292,30 @@ export default {
|
|||||||
return this.lastMessage(this.chat);
|
return this.lastMessage(this.chat);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
messageByAgent() {
|
||||||
|
const lastMessage = this.lastMessageInChat;
|
||||||
|
const { message_type: messageType } = lastMessage;
|
||||||
|
return messageType === MESSAGE_TYPE.OUTGOING;
|
||||||
|
},
|
||||||
|
|
||||||
|
isMessageAnActivity() {
|
||||||
|
const lastMessage = this.lastMessageInChat;
|
||||||
|
const { message_type: messageType } = lastMessage;
|
||||||
|
return messageType === MESSAGE_TYPE.ACTIVITY;
|
||||||
|
},
|
||||||
|
|
||||||
|
isMessagePrivate() {
|
||||||
|
const lastMessage = this.lastMessageInChat;
|
||||||
|
const { private: isPrivate } = lastMessage;
|
||||||
|
return isPrivate;
|
||||||
|
},
|
||||||
|
|
||||||
|
parsedLastMessage() {
|
||||||
|
const { content_attributes: contentAttributes } = this.lastMessageInChat;
|
||||||
|
const { email: { subject } = {} } = contentAttributes || {};
|
||||||
|
return this.getPlainText(subject || this.lastMessageInChat.content);
|
||||||
|
},
|
||||||
|
|
||||||
inbox() {
|
inbox() {
|
||||||
const { inbox_id: inboxId } = this.chat;
|
const { inbox_id: inboxId } = this.chat;
|
||||||
const stateInbox = this.$store.getters['inboxes/getInbox'](inboxId);
|
const stateInbox = this.$store.getters['inboxes/getInbox'](inboxId);
|
||||||
|
|||||||
@@ -3,10 +3,10 @@
|
|||||||
class="bg-white dark:bg-slate-900 flex justify-between items-center py-2 px-4 border-b border-slate-50 dark:border-slate-800/50 flex-col md:flex-row"
|
class="bg-white dark:bg-slate-900 flex justify-between items-center py-2 px-4 border-b border-slate-50 dark:border-slate-800/50 flex-col md:flex-row"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex-1 w-full min-w-0 flex flex-col md:flex-row items-center justify-center"
|
class="flex-1 w-100 flex flex-col md:flex-row items-center justify-center"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="flex justify-start items-center mr-4 rtl:mr-0 rtl:ml-4 min-w-0 w-[inherit]"
|
class="flex justify-center items-center mr-4 rtl:mr-0 rtl:ml-4 min-w-0"
|
||||||
>
|
>
|
||||||
<back-button
|
<back-button
|
||||||
v-if="showBackButton"
|
v-if="showBackButton"
|
||||||
@@ -19,31 +19,26 @@
|
|||||||
:username="currentContact.name"
|
:username="currentContact.name"
|
||||||
:status="currentContact.availability_status"
|
:status="currentContact.availability_status"
|
||||||
/>
|
/>
|
||||||
<div
|
<div class="items-start flex flex-col ml-2 rtl:ml-0 rtl:mr-2 min-w-0">
|
||||||
class="items-start flex flex-col ml-2 rtl:ml-0 rtl:mr-2 min-w-0 w-[inherit] overflow-hidden"
|
<woot-button
|
||||||
>
|
variant="link"
|
||||||
<div class="flex items-center flex-row gap-1 m-0 p-0 w-[inherit]">
|
color-scheme="secondary"
|
||||||
<woot-button
|
class="overflow-hidden whitespace-nowrap text-ellipsis"
|
||||||
variant="link"
|
@click.prevent="$emit('contact-panel-toggle')"
|
||||||
color-scheme="secondary"
|
>
|
||||||
class="[&>span]:overflow-hidden [&>span]:whitespace-nowrap [&>span]:text-ellipsis min-w-0"
|
<h3
|
||||||
@click.prevent="$emit('contact-panel-toggle')"
|
class="text-base inline-block leading-tight capitalize m-0 p-0 text-ellipsis overflow-hidden whitespace-nowrap text-slate-900 dark:text-slate-100"
|
||||||
>
|
>
|
||||||
<span
|
<span>{{ currentContact.name }}</span>
|
||||||
class="text-base leading-tight text-slate-900 dark:text-slate-100"
|
<fluent-icon
|
||||||
>
|
v-if="!isHMACVerified"
|
||||||
{{ currentContact.name }}
|
v-tooltip="$t('CONVERSATION.UNVERIFIED_SESSION')"
|
||||||
</span>
|
size="14"
|
||||||
</woot-button>
|
class="text-yellow-600 dark:text-yellow-500 my-0 mx-0.5"
|
||||||
<fluent-icon
|
icon="warning"
|
||||||
v-if="!isHMACVerified"
|
/>
|
||||||
v-tooltip="$t('CONVERSATION.UNVERIFIED_SESSION')"
|
</h3>
|
||||||
size="14"
|
</woot-button>
|
||||||
class="text-yellow-600 dark:text-yellow-500 my-0 mx-0 min-w-[14px]"
|
|
||||||
icon="warning"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="conversation--header--actions items-center flex text-xs gap-2 text-ellipsis overflow-hidden whitespace-nowrap"
|
class="conversation--header--actions items-center flex text-xs gap-2 text-ellipsis overflow-hidden whitespace-nowrap"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -34,11 +34,6 @@
|
|||||||
:url="storyUrl"
|
:url="storyUrl"
|
||||||
/>
|
/>
|
||||||
</blockquote>
|
</blockquote>
|
||||||
<bubble-reply-to
|
|
||||||
v-if="inReplyToMessageId && inboxSupportsReplyTo"
|
|
||||||
:message="inReplyTo"
|
|
||||||
:message-type="data.message_type"
|
|
||||||
/>
|
|
||||||
<bubble-text
|
<bubble-text
|
||||||
v-if="data.content"
|
v-if="data.content"
|
||||||
:message="message"
|
:message="message"
|
||||||
@@ -129,7 +124,6 @@
|
|||||||
:message="data"
|
:message="data"
|
||||||
@open="openContextMenu"
|
@open="openContextMenu"
|
||||||
@close="closeContextMenu"
|
@close="closeContextMenu"
|
||||||
@replyTo="handleReplyTo"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -146,7 +140,6 @@ import BubbleLocation from './bubble/Location.vue';
|
|||||||
import BubbleMailHead from './bubble/MailHead.vue';
|
import BubbleMailHead from './bubble/MailHead.vue';
|
||||||
import BubbleText from './bubble/Text.vue';
|
import BubbleText from './bubble/Text.vue';
|
||||||
import BubbleContact from './bubble/Contact.vue';
|
import BubbleContact from './bubble/Contact.vue';
|
||||||
import BubbleReplyTo from './bubble/ReplyTo.vue';
|
|
||||||
import Spinner from 'shared/components/Spinner.vue';
|
import Spinner from 'shared/components/Spinner.vue';
|
||||||
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu.vue';
|
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu.vue';
|
||||||
import instagramImageErrorPlaceholder from './instagramImageErrorPlaceholder.vue';
|
import instagramImageErrorPlaceholder from './instagramImageErrorPlaceholder.vue';
|
||||||
@@ -156,8 +149,6 @@ import { MESSAGE_TYPE, MESSAGE_STATUS } from 'shared/constants/messages';
|
|||||||
import { generateBotMessageContent } from './helpers/botMessageContentHelper';
|
import { generateBotMessageContent } from './helpers/botMessageContentHelper';
|
||||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||||
import { ACCOUNT_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
import { ACCOUNT_EVENTS } from 'dashboard/helper/AnalyticsHelper/events';
|
||||||
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
|
|
||||||
import { LocalStorage } from 'shared/helpers/localStorage';
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
@@ -171,7 +162,6 @@ export default {
|
|||||||
BubbleMailHead,
|
BubbleMailHead,
|
||||||
BubbleText,
|
BubbleText,
|
||||||
BubbleContact,
|
BubbleContact,
|
||||||
BubbleReplyTo,
|
|
||||||
ContextMenu,
|
ContextMenu,
|
||||||
Spinner,
|
Spinner,
|
||||||
instagramImageErrorPlaceholder,
|
instagramImageErrorPlaceholder,
|
||||||
@@ -198,14 +188,6 @@ export default {
|
|||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
inboxSupportsReplyTo: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
inReplyTo: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({}),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -282,19 +264,11 @@ export default {
|
|||||||
) + botMessageContent
|
) + botMessageContent
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
inReplyToMessageId() {
|
|
||||||
// Why not use the inReplyTo object directly?
|
|
||||||
// Glad you asked! The inReplyTo object may or may not be available
|
|
||||||
// depending on the current scroll position of the message list
|
|
||||||
// since old messages are only loaded when the user scrolls up
|
|
||||||
return this.data.content_attributes?.in_reply_to;
|
|
||||||
},
|
|
||||||
contextMenuEnabledOptions() {
|
contextMenuEnabledOptions() {
|
||||||
return {
|
return {
|
||||||
copy: this.hasText,
|
copy: this.hasText,
|
||||||
delete: this.hasText || this.hasAttachments,
|
delete: this.hasText || this.hasAttachments,
|
||||||
cannedResponse: this.isOutgoing && this.hasText,
|
cannedResponse: this.isOutgoing && this.hasText,
|
||||||
replyTo: !this.data.private && this.inboxSupportsReplyTo,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
contentAttributes() {
|
contentAttributes() {
|
||||||
@@ -344,8 +318,6 @@ export default {
|
|||||||
left: isLeftAligned,
|
left: isLeftAligned,
|
||||||
right: isRightAligned,
|
right: isRightAligned,
|
||||||
'has-context-menu': this.showContextMenu,
|
'has-context-menu': this.showContextMenu,
|
||||||
// this handles the offset required to align the context menu button
|
|
||||||
// extra alignment is required since a tweet message has a the user name and avatar below it
|
|
||||||
'has-tweet-menu': this.isATweet,
|
'has-tweet-menu': this.isATweet,
|
||||||
'has-bg': this.showBackgroundHighlight,
|
'has-bg': this.showBackgroundHighlight,
|
||||||
};
|
};
|
||||||
@@ -492,10 +464,7 @@ export default {
|
|||||||
this.showContextMenu = !this.showContextMenu;
|
this.showContextMenu = !this.showContextMenu;
|
||||||
},
|
},
|
||||||
async retrySendMessage() {
|
async retrySendMessage() {
|
||||||
await this.$store.dispatch('sendMessageWithData', {
|
await this.$store.dispatch('sendMessageWithData', this.data);
|
||||||
conversationId: this.data.conversation_id,
|
|
||||||
...this.data,
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
onImageLoadError() {
|
onImageLoadError() {
|
||||||
this.hasImageError = true;
|
this.hasImageError = true;
|
||||||
@@ -525,13 +494,6 @@ export default {
|
|||||||
this.showContextMenu = false;
|
this.showContextMenu = false;
|
||||||
this.contextMenuPosition = { x: null, y: null };
|
this.contextMenuPosition = { x: null, y: null };
|
||||||
},
|
},
|
||||||
handleReplyTo() {
|
|
||||||
const replyStorageKey = LOCAL_STORAGE_KEYS.MESSAGE_REPLY_TO;
|
|
||||||
const { conversation_id: conversationId, id: replyTo } = this.data;
|
|
||||||
|
|
||||||
LocalStorage.updateJsonStore(replyStorageKey, conversationId, replyTo);
|
|
||||||
bus.$emit(BUS_EVENTS.TOGGLE_REPLY_TO_MESSAGE, this.data);
|
|
||||||
},
|
|
||||||
setupHighlightTimer() {
|
setupHighlightTimer() {
|
||||||
if (Number(this.$route.query.messageId) !== Number(this.data.id)) {
|
if (Number(this.$route.query.messageId) !== Number(this.data.id)) {
|
||||||
return;
|
return;
|
||||||
@@ -651,8 +613,6 @@ li.right {
|
|||||||
}
|
}
|
||||||
|
|
||||||
li.left.has-tweet-menu .context-menu {
|
li.left.has-tweet-menu .context-menu {
|
||||||
// this handles the offset required to align the context menu button
|
|
||||||
// extra alignment is required since a tweet message has a the user name and avatar below it
|
|
||||||
@apply mb-6;
|
@apply mb-6;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,93 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="overflow-hidden text-ellipsis whitespace-nowrap">
|
|
||||||
<template v-if="showMessageType">
|
|
||||||
<fluent-icon
|
|
||||||
v-if="isMessagePrivate"
|
|
||||||
size="16"
|
|
||||||
class="-mt-0.5 align-middle text-slate-600 dark:text-slate-300 inline-block"
|
|
||||||
icon="lock-closed"
|
|
||||||
/>
|
|
||||||
<fluent-icon
|
|
||||||
v-else-if="messageByAgent"
|
|
||||||
size="16"
|
|
||||||
class="-mt-0.5 align-middle text-slate-600 dark:text-slate-300 inline-block"
|
|
||||||
icon="arrow-reply"
|
|
||||||
/>
|
|
||||||
<fluent-icon
|
|
||||||
v-else-if="isMessageAnActivity"
|
|
||||||
size="16"
|
|
||||||
class="-mt-0.5 align-middle text-slate-600 dark:text-slate-300 inline-block"
|
|
||||||
icon="info"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
<span v-if="message.content">
|
|
||||||
{{ parsedLastMessage }}
|
|
||||||
</span>
|
|
||||||
<span v-else-if="message.attachments">
|
|
||||||
<fluent-icon
|
|
||||||
v-if="attachmentIcon && showMessageType"
|
|
||||||
size="16"
|
|
||||||
class="-mt-0.5 align-middle inline-block text-slate-600 dark:text-slate-300"
|
|
||||||
:icon="attachmentIcon"
|
|
||||||
/>
|
|
||||||
{{ $t(`${attachmentMessageContent}`) }}
|
|
||||||
</span>
|
|
||||||
<span v-else>
|
|
||||||
{{ defaultEmptyMessage || $t('CHAT_LIST.NO_CONTENT') }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
|
||||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
|
||||||
import { ATTACHMENT_ICONS } from 'shared/constants/messages';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'MessagePreview',
|
|
||||||
mixins: [messageFormatterMixin],
|
|
||||||
props: {
|
|
||||||
message: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
showMessageType: {
|
|
||||||
type: Boolean,
|
|
||||||
default: true,
|
|
||||||
},
|
|
||||||
defaultEmptyMessage: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
computed: {
|
|
||||||
messageByAgent() {
|
|
||||||
const { message_type: messageType } = this.message;
|
|
||||||
return messageType === MESSAGE_TYPE.OUTGOING;
|
|
||||||
},
|
|
||||||
isMessageAnActivity() {
|
|
||||||
const { message_type: messageType } = this.message;
|
|
||||||
return messageType === MESSAGE_TYPE.ACTIVITY;
|
|
||||||
},
|
|
||||||
isMessagePrivate() {
|
|
||||||
const { private: isPrivate } = this.message;
|
|
||||||
return isPrivate;
|
|
||||||
},
|
|
||||||
parsedLastMessage() {
|
|
||||||
const { content_attributes: contentAttributes } = this.message;
|
|
||||||
const { email: { subject } = {} } = contentAttributes || {};
|
|
||||||
return this.getPlainText(subject || this.message.content);
|
|
||||||
},
|
|
||||||
lastMessageFileType() {
|
|
||||||
const [{ file_type: fileType } = {}] = this.message.attachments;
|
|
||||||
return fileType;
|
|
||||||
},
|
|
||||||
attachmentIcon() {
|
|
||||||
return ATTACHMENT_ICONS[this.lastMessageFileType];
|
|
||||||
},
|
|
||||||
attachmentMessageContent() {
|
|
||||||
return `CHAT_LIST.ATTACHMENTS.${this.lastMessageFileType}.CONTENT`;
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col justify-between flex-grow h-full min-w-0 m-0">
|
<div class="m-0 flex flex-col justify-between h-full flex-grow min-w-0">
|
||||||
<banner
|
<banner
|
||||||
v-if="!currentChat.can_reply"
|
v-if="!currentChat.can_reply"
|
||||||
color-scheme="alert"
|
color-scheme="alert"
|
||||||
@@ -7,12 +7,21 @@
|
|||||||
:href-link="replyWindowLink"
|
:href-link="replyWindowLink"
|
||||||
:href-link-text="replyWindowLinkText"
|
:href-link-text="replyWindowLinkText"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<banner
|
||||||
|
v-if="isATweet"
|
||||||
|
color-scheme="gray"
|
||||||
|
:banner-message="tweetBannerText"
|
||||||
|
:has-close-button="hasSelectedTweetId"
|
||||||
|
@close="removeTweetSelection"
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="flex justify-end">
|
<div class="flex justify-end">
|
||||||
<woot-button
|
<woot-button
|
||||||
variant="smooth"
|
variant="smooth"
|
||||||
size="tiny"
|
size="tiny"
|
||||||
color-scheme="secondary"
|
color-scheme="secondary"
|
||||||
class="rounded-bl-calc rtl:rotate-180 rounded-tl-calc fixed top-[9.5rem] md:top-[6.25rem] z-10 bg-white dark:bg-slate-700 border-slate-50 dark:border-slate-600 border-solid border border-r-0 box-border"
|
class="rounded-bl-calc rtl:rotate-180 rounded-tl-calc fixed top-[6.25rem] z-10 bg-white dark:bg-slate-700 border-slate-50 dark:border-slate-600 border-solid border border-r-0 box-border"
|
||||||
:icon="isRightOrLeftIcon"
|
:icon="isRightOrLeftIcon"
|
||||||
@click="onToggleContactPanel"
|
@click="onToggleContactPanel"
|
||||||
/>
|
/>
|
||||||
@@ -33,8 +42,6 @@
|
|||||||
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
||||||
:has-instagram-story="hasInstagramStory"
|
:has-instagram-story="hasInstagramStory"
|
||||||
:is-web-widget-inbox="isAWebWidgetInbox"
|
:is-web-widget-inbox="isAWebWidgetInbox"
|
||||||
:inbox-supports-reply-to="inboxSupportsReplyTo"
|
|
||||||
:in-reply-to="getInReplyToMessage(message)"
|
|
||||||
/>
|
/>
|
||||||
<li v-show="unreadMessageCount != 0" class="unread--toast">
|
<li v-show="unreadMessageCount != 0" class="unread--toast">
|
||||||
<span>
|
<span>
|
||||||
@@ -56,8 +63,6 @@
|
|||||||
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
:is-a-whatsapp-channel="isAWhatsAppChannel"
|
||||||
:has-instagram-story="hasInstagramStory"
|
:has-instagram-story="hasInstagramStory"
|
||||||
:is-web-widget-inbox="isAWebWidgetInbox"
|
:is-web-widget-inbox="isAWebWidgetInbox"
|
||||||
:inbox-supports-reply-to="inboxSupportsReplyTo"
|
|
||||||
:in-reply-to="getInReplyToMessage(message)"
|
|
||||||
/>
|
/>
|
||||||
<conversation-label-suggestion
|
<conversation-label-suggestion
|
||||||
v-if="shouldShowLabelSuggestions"
|
v-if="shouldShowLabelSuggestions"
|
||||||
@@ -82,6 +87,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<reply-box
|
<reply-box
|
||||||
:conversation-id="currentChat.id"
|
:conversation-id="currentChat.id"
|
||||||
|
:is-a-tweet="isATweet"
|
||||||
|
:selected-tweet="selectedTweet"
|
||||||
:popout-reply-box.sync="isPopoutReplyBox"
|
:popout-reply-box.sync="isPopoutReplyBox"
|
||||||
@click="showPopoutReplyBox"
|
@click="showPopoutReplyBox"
|
||||||
/>
|
/>
|
||||||
@@ -103,7 +110,7 @@ import { mapGetters } from 'vuex';
|
|||||||
import conversationMixin, {
|
import conversationMixin, {
|
||||||
filterDuplicateSourceMessages,
|
filterDuplicateSourceMessages,
|
||||||
} from '../../../mixins/conversations';
|
} from '../../../mixins/conversations';
|
||||||
import inboxMixin, { INBOX_FEATURES } from 'shared/mixins/inboxMixin';
|
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||||
import configMixin from 'shared/mixins/configMixin';
|
import configMixin from 'shared/mixins/configMixin';
|
||||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||||
import aiMixin from 'dashboard/mixins/aiMixin';
|
import aiMixin from 'dashboard/mixins/aiMixin';
|
||||||
@@ -116,7 +123,6 @@ import { LocalStorage } from 'shared/helpers/localStorage';
|
|||||||
|
|
||||||
// constants
|
// constants
|
||||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
|
||||||
import { REPLY_POLICY } from 'shared/constants/links';
|
import { REPLY_POLICY } from 'shared/constants/links';
|
||||||
import wootConstants from 'dashboard/constants/globals';
|
import wootConstants from 'dashboard/constants/globals';
|
||||||
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
|
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
|
||||||
@@ -147,6 +153,7 @@ export default {
|
|||||||
isLoadingPrevious: true,
|
isLoadingPrevious: true,
|
||||||
heightBeforeLoad: null,
|
heightBeforeLoad: null,
|
||||||
conversationPanel: null,
|
conversationPanel: null,
|
||||||
|
selectedTweetId: null,
|
||||||
hasUserScrolled: false,
|
hasUserScrolled: false,
|
||||||
isProgrammaticScroll: false,
|
isProgrammaticScroll: false,
|
||||||
isPopoutReplyBox: false,
|
isPopoutReplyBox: false,
|
||||||
@@ -157,14 +164,12 @@ export default {
|
|||||||
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapGetters({
|
...mapGetters({
|
||||||
accountId: 'getCurrentAccountId',
|
|
||||||
currentChat: 'getSelectedChat',
|
currentChat: 'getSelectedChat',
|
||||||
allConversations: 'getAllConversations',
|
allConversations: 'getAllConversations',
|
||||||
inboxesList: 'inboxes/getInboxes',
|
inboxesList: 'inboxes/getInboxes',
|
||||||
listLoadingStatus: 'getAllMessagesLoaded',
|
listLoadingStatus: 'getAllMessagesLoaded',
|
||||||
loadingChatList: 'getChatListLoadingStatus',
|
loadingChatList: 'getChatListLoadingStatus',
|
||||||
appIntegrations: 'integrations/getAppIntegrations',
|
appIntegrations: 'integrations/getAppIntegrations',
|
||||||
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
|
||||||
currentAccountId: 'getCurrentAccountId',
|
currentAccountId: 'getCurrentAccountId',
|
||||||
}),
|
}),
|
||||||
isOpen() {
|
isOpen() {
|
||||||
@@ -184,6 +189,16 @@ export default {
|
|||||||
inbox() {
|
inbox() {
|
||||||
return this.$store.getters['inboxes/getInbox'](this.inboxId);
|
return this.$store.getters['inboxes/getInbox'](this.inboxId);
|
||||||
},
|
},
|
||||||
|
hasSelectedTweetId() {
|
||||||
|
return !!this.selectedTweetId;
|
||||||
|
},
|
||||||
|
tweetBannerText() {
|
||||||
|
return !this.selectedTweetId
|
||||||
|
? this.$t('CONVERSATION.SELECT_A_TWEET_TO_REPLY')
|
||||||
|
: `
|
||||||
|
${this.$t('CONVERSATION.REPLYING_TO')}
|
||||||
|
${this.selectedTweet.content}` || '';
|
||||||
|
},
|
||||||
typingUsersList() {
|
typingUsersList() {
|
||||||
const userList = this.$store.getters[
|
const userList = this.$store.getters[
|
||||||
'conversationTypingStatus/getUserList'
|
'conversationTypingStatus/getUserList'
|
||||||
@@ -242,6 +257,17 @@ export default {
|
|||||||
hasInstagramStory() {
|
hasInstagramStory() {
|
||||||
return this.conversationType === 'instagram_direct_message';
|
return this.conversationType === 'instagram_direct_message';
|
||||||
},
|
},
|
||||||
|
|
||||||
|
selectedTweet() {
|
||||||
|
if (this.selectedTweetId) {
|
||||||
|
const { messages = [] } = this.currentChat;
|
||||||
|
const [selectedMessage] = messages.filter(
|
||||||
|
message => message.id === this.selectedTweetId
|
||||||
|
);
|
||||||
|
return selectedMessage || {};
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
isRightOrLeftIcon() {
|
isRightOrLeftIcon() {
|
||||||
if (this.isContactPanelOpen) {
|
if (this.isContactPanelOpen) {
|
||||||
return 'arrow-chevron-right';
|
return 'arrow-chevron-right';
|
||||||
@@ -290,15 +316,6 @@ export default {
|
|||||||
unreadMessageCount() {
|
unreadMessageCount() {
|
||||||
return this.currentChat.unread_count || 0;
|
return this.currentChat.unread_count || 0;
|
||||||
},
|
},
|
||||||
inboxSupportsReplyTo() {
|
|
||||||
return (
|
|
||||||
this.inboxHasFeature(INBOX_FEATURES.REPLY_TO) &&
|
|
||||||
this.isFeatureEnabledonAccount(
|
|
||||||
this.accountId,
|
|
||||||
FEATURE_FLAGS.MESSAGE_REPLY_TO
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
@@ -309,6 +326,7 @@ export default {
|
|||||||
this.fetchAllAttachmentsFromCurrentChat();
|
this.fetchAllAttachmentsFromCurrentChat();
|
||||||
this.fetchSuggestions();
|
this.fetchSuggestions();
|
||||||
this.messageSentSinceOpened = false;
|
this.messageSentSinceOpened = false;
|
||||||
|
this.selectedTweetId = null;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -316,6 +334,7 @@ export default {
|
|||||||
bus.$on(BUS_EVENTS.SCROLL_TO_MESSAGE, this.onScrollToMessage);
|
bus.$on(BUS_EVENTS.SCROLL_TO_MESSAGE, this.onScrollToMessage);
|
||||||
// when a new message comes in, we refetch the label suggestions
|
// when a new message comes in, we refetch the label suggestions
|
||||||
bus.$on(BUS_EVENTS.FETCH_LABEL_SUGGESTIONS, this.fetchSuggestions);
|
bus.$on(BUS_EVENTS.FETCH_LABEL_SUGGESTIONS, this.fetchSuggestions);
|
||||||
|
bus.$on(BUS_EVENTS.SET_TWEET_REPLY, this.setSelectedTweet);
|
||||||
// when a message is sent we set the flag to true this hides the label suggestions,
|
// when a message is sent we set the flag to true this hides the label suggestions,
|
||||||
// until the chat is changed and the flag is reset in the watch for currentChat
|
// until the chat is changed and the flag is reset in the watch for currentChat
|
||||||
bus.$on(BUS_EVENTS.MESSAGE_SENT, () => {
|
bus.$on(BUS_EVENTS.MESSAGE_SENT, () => {
|
||||||
@@ -386,6 +405,10 @@ export default {
|
|||||||
},
|
},
|
||||||
removeBusListeners() {
|
removeBusListeners() {
|
||||||
bus.$off(BUS_EVENTS.SCROLL_TO_MESSAGE, this.onScrollToMessage);
|
bus.$off(BUS_EVENTS.SCROLL_TO_MESSAGE, this.onScrollToMessage);
|
||||||
|
bus.$off(BUS_EVENTS.SET_TWEET_REPLY, this.setSelectedTweet);
|
||||||
|
},
|
||||||
|
setSelectedTweet(tweetId) {
|
||||||
|
this.selectedTweetId = tweetId;
|
||||||
},
|
},
|
||||||
onScrollToMessage({ messageId = '' } = {}) {
|
onScrollToMessage({ messageId = '' } = {}) {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
@@ -508,18 +531,8 @@ export default {
|
|||||||
makeMessagesRead() {
|
makeMessagesRead() {
|
||||||
this.$store.dispatch('markMessagesRead', { id: this.currentChat.id });
|
this.$store.dispatch('markMessagesRead', { id: this.currentChat.id });
|
||||||
},
|
},
|
||||||
|
removeTweetSelection() {
|
||||||
getInReplyToMessage(parentMessage) {
|
this.selectedTweetId = null;
|
||||||
if (!parentMessage) return {};
|
|
||||||
const inReplyToMessageId = parentMessage.content_attributes?.in_reply_to;
|
|
||||||
if (!inReplyToMessageId) return {};
|
|
||||||
|
|
||||||
return this.currentChat?.messages.find(message => {
|
|
||||||
if (message.id === inReplyToMessageId) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="reply-box" :class="replyBoxClass">
|
<div
|
||||||
|
class="border-t border-slate-50 dark:border-slate-700 bg-white dark:bg-slate-900 reply-box"
|
||||||
|
:class="{
|
||||||
|
'bg-yellow-100 dark:bg-yellow-800 is-private': isPrivate,
|
||||||
|
'shadow-sm': isFocused || hasAttachments,
|
||||||
|
}"
|
||||||
|
>
|
||||||
<banner
|
<banner
|
||||||
v-if="showSelfAssignBanner"
|
v-if="showSelfAssignBanner"
|
||||||
action-button-variant="clear"
|
action-button-variant="clear"
|
||||||
@@ -19,11 +25,6 @@
|
|||||||
@click="$emit('click')"
|
@click="$emit('click')"
|
||||||
/>
|
/>
|
||||||
<div class="reply-box__top">
|
<div class="reply-box__top">
|
||||||
<reply-to-message
|
|
||||||
v-if="shouldShowReplyToMessage"
|
|
||||||
:message="inReplyTo"
|
|
||||||
@dismiss="resetReplyToMessage"
|
|
||||||
/>
|
|
||||||
<canned-response
|
<canned-response
|
||||||
v-if="showMentions && hasSlashCommand"
|
v-if="showMentions && hasSlashCommand"
|
||||||
v-on-clickaway="hideMentions"
|
v-on-clickaway="hideMentions"
|
||||||
@@ -91,7 +92,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-if="hasAttachments" class="attachment-preview-box" @paste="onPaste">
|
<div v-if="hasAttachments" class="attachment-preview-box" @paste="onPaste">
|
||||||
<attachment-preview
|
<attachment-preview
|
||||||
class="flex-col mt-4"
|
|
||||||
:attachments="attachedFiles"
|
:attachments="attachedFiles"
|
||||||
:remove-attachment="removeAttachment"
|
:remove-attachment="removeAttachment"
|
||||||
/>
|
/>
|
||||||
@@ -121,7 +121,6 @@
|
|||||||
:toggle-audio-recorder="toggleAudioRecorder"
|
:toggle-audio-recorder="toggleAudioRecorder"
|
||||||
:toggle-emoji-picker="toggleEmojiPicker"
|
:toggle-emoji-picker="toggleEmojiPicker"
|
||||||
:message="message"
|
:message="message"
|
||||||
:new-conversation-modal-active="newConversationModalActive"
|
|
||||||
@selectWhatsappTemplate="openWhatsappTemplateModal"
|
@selectWhatsappTemplate="openWhatsappTemplateModal"
|
||||||
@toggle-editor="toggleRichContentEditor"
|
@toggle-editor="toggleRichContentEditor"
|
||||||
@replace-text="replaceText"
|
@replace-text="replaceText"
|
||||||
@@ -143,59 +142,63 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
// Vuex
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
|
|
||||||
|
// Mixins
|
||||||
import { mixin as clickaway } from 'vue-clickaway';
|
import { mixin as clickaway } from 'vue-clickaway';
|
||||||
import alertMixin from 'shared/mixins/alertMixin';
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
|
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||||
import CannedResponse from './CannedResponse.vue';
|
|
||||||
import ReplyToMessage from './ReplyToMessage.vue';
|
|
||||||
import ResizableTextArea from 'shared/components/ResizableTextArea.vue';
|
|
||||||
import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview.vue';
|
|
||||||
import ReplyTopPanel from 'dashboard/components/widgets/WootWriter/ReplyTopPanel.vue';
|
|
||||||
import ReplyEmailHead from './ReplyEmailHead.vue';
|
|
||||||
import ReplyBottomPanel from 'dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue';
|
|
||||||
import MessageSignatureMissingAlert from './MessageSignatureMissingAlert';
|
|
||||||
import Banner from 'dashboard/components/ui/Banner.vue';
|
|
||||||
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
|
|
||||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
|
|
||||||
import WootAudioRecorder from 'dashboard/components/widgets/WootWriter/AudioRecorder.vue';
|
|
||||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||||
import { AUDIO_FORMATS } from 'shared/constants/messages';
|
|
||||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
|
||||||
import {
|
|
||||||
getMessageVariables,
|
|
||||||
getUndefinedVariablesInMessage,
|
|
||||||
replaceVariablesInMessage,
|
|
||||||
} from '@chatwoot/utils';
|
|
||||||
import WhatsappTemplates from './WhatsappTemplates/Modal.vue';
|
|
||||||
import { buildHotKeys } from 'shared/helpers/KeyboardHelpers';
|
|
||||||
import { MESSAGE_MAX_LENGTH } from 'shared/helpers/MessageTypeHelper';
|
|
||||||
import inboxMixin, { INBOX_FEATURES } from 'shared/mixins/inboxMixin';
|
|
||||||
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
|
||||||
import { trimContent, debounce } from '@chatwoot/utils';
|
|
||||||
import wootConstants from 'dashboard/constants/globals';
|
|
||||||
import { isEditorHotKeyEnabled } from 'dashboard/mixins/uiSettings';
|
|
||||||
import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events';
|
|
||||||
import rtlMixin from 'shared/mixins/rtlMixin';
|
import rtlMixin from 'shared/mixins/rtlMixin';
|
||||||
import fileUploadMixin from 'dashboard/mixins/fileUploadMixin';
|
import fileUploadMixin from 'dashboard/mixins/fileUploadMixin';
|
||||||
|
import replyDraftMixin from 'dashboard/mixins/replyDraftMixin';
|
||||||
|
import audioRecordingMixin from 'dashboard/mixins/audioRecordingMixin';
|
||||||
|
import emailEditorMixin from 'dashboard/mixins/emailEditorMixin';
|
||||||
|
import attachmentHandlerMixin from 'dashboard/mixins/attachmentHandlerMixin';
|
||||||
|
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||||
|
|
||||||
|
// constants
|
||||||
|
import { REPLY_EDITOR_MODES } from 'dashboard/components/widgets/WootWriter/constants';
|
||||||
|
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||||
|
import { MESSAGE_MAX_LENGTH } from 'shared/helpers/MessageTypeHelper';
|
||||||
|
import { CONVERSATION_EVENTS } from '../../../helper/AnalyticsHelper/events';
|
||||||
|
import wootConstants from 'dashboard/constants/globals';
|
||||||
|
|
||||||
|
// components
|
||||||
|
import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview.vue';
|
||||||
|
import Banner from 'dashboard/components/ui/Banner.vue';
|
||||||
|
import CannedResponse from './CannedResponse.vue';
|
||||||
|
import EmojiInput from 'shared/components/emoji/EmojiInput.vue';
|
||||||
|
import MessageSignatureMissingAlert from './MessageSignatureMissingAlert.vue';
|
||||||
|
import ReplyBottomPanel from 'dashboard/components/widgets/WootWriter/ReplyBottomPanel.vue';
|
||||||
|
import ReplyEmailHead from './ReplyEmailHead.vue';
|
||||||
|
import ReplyTopPanel from 'dashboard/components/widgets/WootWriter/ReplyTopPanel.vue';
|
||||||
|
import ResizableTextArea from 'shared/components/ResizableTextArea.vue';
|
||||||
|
import WhatsappTemplates from './WhatsappTemplates/Modal.vue';
|
||||||
|
import WootAudioRecorder from 'dashboard/components/widgets/WootWriter/AudioRecorder.vue';
|
||||||
|
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
import { buildHotKeys } from 'shared/helpers/KeyboardHelpers';
|
||||||
|
import { isEditorHotKeyEnabled } from 'dashboard/mixins/uiSettings';
|
||||||
import {
|
import {
|
||||||
appendSignature,
|
appendSignature,
|
||||||
removeSignature,
|
removeSignature,
|
||||||
replaceSignature,
|
replaceSignature,
|
||||||
extractTextFromMarkdown,
|
extractTextFromMarkdown,
|
||||||
} from 'dashboard/helper/editorHelper';
|
} from 'dashboard/helper/editorHelper';
|
||||||
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
|
import {
|
||||||
|
getMessageVariables,
|
||||||
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
|
getUndefinedVariablesInMessage,
|
||||||
import { LocalStorage } from 'shared/helpers/localStorage';
|
replaceVariablesInMessage,
|
||||||
|
debounce,
|
||||||
const EmojiInput = () => import('shared/components/emoji/EmojiInput');
|
} from '@chatwoot/utils';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
EmojiInput,
|
EmojiInput,
|
||||||
CannedResponse,
|
CannedResponse,
|
||||||
ReplyToMessage,
|
|
||||||
ResizableTextArea,
|
ResizableTextArea,
|
||||||
AttachmentPreview,
|
AttachmentPreview,
|
||||||
ReplyTopPanel,
|
ReplyTopPanel,
|
||||||
@@ -215,8 +218,20 @@ export default {
|
|||||||
messageFormatterMixin,
|
messageFormatterMixin,
|
||||||
rtlMixin,
|
rtlMixin,
|
||||||
fileUploadMixin,
|
fileUploadMixin,
|
||||||
|
replyDraftMixin,
|
||||||
|
audioRecordingMixin,
|
||||||
|
emailEditorMixin,
|
||||||
|
attachmentHandlerMixin,
|
||||||
],
|
],
|
||||||
props: {
|
props: {
|
||||||
|
selectedTweet: {
|
||||||
|
type: [Object, String],
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
isATweet: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
popoutReplyBox: {
|
popoutReplyBox: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false,
|
default: false,
|
||||||
@@ -225,20 +240,12 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
message: '',
|
message: '',
|
||||||
inReplyTo: {},
|
|
||||||
isFocused: false,
|
isFocused: false,
|
||||||
showEmojiPicker: false,
|
showEmojiPicker: false,
|
||||||
attachedFiles: [],
|
|
||||||
isRecordingAudio: false,
|
|
||||||
recordingAudioState: '',
|
|
||||||
recordingAudioDurationText: '',
|
|
||||||
isUploading: false,
|
isUploading: false,
|
||||||
replyType: REPLY_EDITOR_MODES.REPLY,
|
replyType: REPLY_EDITOR_MODES.REPLY,
|
||||||
mentionSearchKey: '',
|
mentionSearchKey: '',
|
||||||
hasSlashCommand: false,
|
hasSlashCommand: false,
|
||||||
bccEmails: '',
|
|
||||||
ccEmails: '',
|
|
||||||
toEmails: '',
|
|
||||||
doAutoSaveDraft: () => {},
|
doAutoSaveDraft: () => {},
|
||||||
showWhatsAppTemplatesModal: false,
|
showWhatsAppTemplatesModal: false,
|
||||||
updateEditorSelectionWith: '',
|
updateEditorSelectionWith: '',
|
||||||
@@ -247,7 +254,6 @@ export default {
|
|||||||
showUserMentions: false,
|
showUserMentions: false,
|
||||||
showCannedMenu: false,
|
showCannedMenu: false,
|
||||||
showVariablesMenu: false,
|
showVariablesMenu: false,
|
||||||
newConversationModalActive: false,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
@@ -258,19 +264,7 @@ export default {
|
|||||||
lastEmail: 'getLastEmailInSelectedChat',
|
lastEmail: 'getLastEmailInSelectedChat',
|
||||||
globalConfig: 'globalConfig/get',
|
globalConfig: 'globalConfig/get',
|
||||||
accountId: 'getCurrentAccountId',
|
accountId: 'getCurrentAccountId',
|
||||||
isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
|
|
||||||
}),
|
}),
|
||||||
shouldShowReplyToMessage() {
|
|
||||||
return (
|
|
||||||
this.inReplyTo?.id &&
|
|
||||||
!this.isPrivate &&
|
|
||||||
this.inboxHasFeature(INBOX_FEATURES.REPLY_TO) &&
|
|
||||||
this.isFeatureEnabledonAccount(
|
|
||||||
this.accountId,
|
|
||||||
FEATURE_FLAGS.MESSAGE_REPLY_TO
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
showRichContentEditor() {
|
showRichContentEditor() {
|
||||||
if (this.isOnPrivateNote || this.isRichEditorEnabled) {
|
if (this.isOnPrivateNote || this.isRichEditorEnabled) {
|
||||||
return true;
|
return true;
|
||||||
@@ -342,7 +336,10 @@ export default {
|
|||||||
return this.maxLength - this.message.length;
|
return this.maxLength - this.message.length;
|
||||||
},
|
},
|
||||||
isReplyButtonDisabled() {
|
isReplyButtonDisabled() {
|
||||||
if (this.isATwitterInbox) return true;
|
if (this.isATweet && !this.inReplyTo && !this.isOnPrivateNote) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (this.hasAttachments || this.hasRecordedAudio) return false;
|
if (this.hasAttachments || this.hasRecordedAudio) return false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -375,6 +372,11 @@ export default {
|
|||||||
if (this.isASmsInbox) {
|
if (this.isASmsInbox) {
|
||||||
return MESSAGE_MAX_LENGTH.TWILIO_SMS;
|
return MESSAGE_MAX_LENGTH.TWILIO_SMS;
|
||||||
}
|
}
|
||||||
|
if (this.isATwitterInbox) {
|
||||||
|
if (this.conversationType === 'tweet') {
|
||||||
|
return MESSAGE_MAX_LENGTH.TWEET - this.replyToUserLength - 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
return MESSAGE_MAX_LENGTH.GENERAL;
|
return MESSAGE_MAX_LENGTH.GENERAL;
|
||||||
},
|
},
|
||||||
showFileUpload() {
|
showFileUpload() {
|
||||||
@@ -392,21 +394,14 @@ export default {
|
|||||||
let sendMessageText = this.$t('CONVERSATION.REPLYBOX.SEND');
|
let sendMessageText = this.$t('CONVERSATION.REPLYBOX.SEND');
|
||||||
if (this.isPrivate) {
|
if (this.isPrivate) {
|
||||||
sendMessageText = this.$t('CONVERSATION.REPLYBOX.CREATE');
|
sendMessageText = this.$t('CONVERSATION.REPLYBOX.CREATE');
|
||||||
|
} else if (this.conversationType === 'tweet') {
|
||||||
|
sendMessageText = this.$t('CONVERSATION.REPLYBOX.TWEET');
|
||||||
}
|
}
|
||||||
const keyLabel = isEditorHotKeyEnabled(this.uiSettings, 'cmd_enter')
|
const keyLabel = isEditorHotKeyEnabled(this.uiSettings, 'cmd_enter')
|
||||||
? '(⌘ + ↵)'
|
? '(⌘ + ↵)'
|
||||||
: '(↵)';
|
: '(↵)';
|
||||||
return `${sendMessageText} ${keyLabel}`;
|
return `${sendMessageText} ${keyLabel}`;
|
||||||
},
|
},
|
||||||
replyBoxClass() {
|
|
||||||
return {
|
|
||||||
'is-private': this.isPrivate,
|
|
||||||
'is-focused': this.isFocused || this.hasAttachments,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
hasAttachments() {
|
|
||||||
return this.attachedFiles.length;
|
|
||||||
},
|
|
||||||
hasRecordedAudio() {
|
hasRecordedAudio() {
|
||||||
return (
|
return (
|
||||||
this.$refs.audioRecorderInput &&
|
this.$refs.audioRecorderInput &&
|
||||||
@@ -416,15 +411,13 @@ export default {
|
|||||||
isRichEditorEnabled() {
|
isRichEditorEnabled() {
|
||||||
return this.isAWebWidgetInbox || this.isAnEmailChannel;
|
return this.isAWebWidgetInbox || this.isAnEmailChannel;
|
||||||
},
|
},
|
||||||
showAudioRecorder() {
|
|
||||||
return !this.isOnPrivateNote && this.showFileUpload;
|
|
||||||
},
|
|
||||||
showAudioRecorderEditor() {
|
|
||||||
return this.showAudioRecorder && this.isRecordingAudio;
|
|
||||||
},
|
|
||||||
isOnPrivateNote() {
|
isOnPrivateNote() {
|
||||||
return this.replyType === REPLY_EDITOR_MODES.NOTE;
|
return this.replyType === REPLY_EDITOR_MODES.NOTE;
|
||||||
},
|
},
|
||||||
|
inReplyTo() {
|
||||||
|
const selectedTweet = this.selectedTweet || {};
|
||||||
|
return selectedTweet.id;
|
||||||
|
},
|
||||||
isOnExpandedLayout() {
|
isOnExpandedLayout() {
|
||||||
const {
|
const {
|
||||||
LAYOUT_TYPES: { CONDENSED },
|
LAYOUT_TYPES: { CONDENSED },
|
||||||
@@ -442,6 +435,15 @@ export default {
|
|||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
},
|
},
|
||||||
|
replyToUserLength() {
|
||||||
|
const selectedTweet = this.selectedTweet || {};
|
||||||
|
const {
|
||||||
|
sender: {
|
||||||
|
additional_attributes: { screen_name: screenName = '' } = {},
|
||||||
|
} = {},
|
||||||
|
} = selectedTweet;
|
||||||
|
return screenName ? screenName.length : 0;
|
||||||
|
},
|
||||||
isMessageEmpty() {
|
isMessageEmpty() {
|
||||||
if (!this.message) {
|
if (!this.message) {
|
||||||
return true;
|
return true;
|
||||||
@@ -488,12 +490,6 @@ export default {
|
|||||||
editorStateId() {
|
editorStateId() {
|
||||||
return `draft-${this.conversationIdByRoute}-${this.replyType}`;
|
return `draft-${this.conversationIdByRoute}-${this.replyType}`;
|
||||||
},
|
},
|
||||||
audioRecordFormat() {
|
|
||||||
if (this.isAWhatsAppChannel || this.isAPIInbox) {
|
|
||||||
return AUDIO_FORMATS.OGG;
|
|
||||||
}
|
|
||||||
return AUDIO_FORMATS.WAV;
|
|
||||||
},
|
|
||||||
messageVariables() {
|
messageVariables() {
|
||||||
const variables = getMessageVariables({
|
const variables = getMessageVariables({
|
||||||
conversation: this.currentChat,
|
conversation: this.currentChat,
|
||||||
@@ -522,7 +518,6 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.setCCAndToEmailsFromLastChat();
|
this.setCCAndToEmailsFromLastChat();
|
||||||
this.fetchAndSetReplyTo();
|
|
||||||
},
|
},
|
||||||
conversationIdByRoute(conversationId, oldConversationId) {
|
conversationIdByRoute(conversationId, oldConversationId) {
|
||||||
if (conversationId !== oldConversationId) {
|
if (conversationId !== oldConversationId) {
|
||||||
@@ -564,28 +559,10 @@ export default {
|
|||||||
500,
|
500,
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
this.fetchAndSetReplyTo();
|
|
||||||
bus.$on(BUS_EVENTS.TOGGLE_REPLY_TO_MESSAGE, this.fetchAndSetReplyTo);
|
|
||||||
|
|
||||||
// A hacky fix to solve the drag and drop
|
|
||||||
// Is showing on top of new conversation modal drag and drop
|
|
||||||
// TODO need to find a better solution
|
|
||||||
bus.$on(
|
|
||||||
BUS_EVENTS.NEW_CONVERSATION_MODAL,
|
|
||||||
this.onNewConversationModalActive
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
destroyed() {
|
destroyed() {
|
||||||
document.removeEventListener('paste', this.onPaste);
|
document.removeEventListener('paste', this.onPaste);
|
||||||
document.removeEventListener('keydown', this.handleKeyEvents);
|
document.removeEventListener('keydown', this.handleKeyEvents);
|
||||||
bus.$off(BUS_EVENTS.TOGGLE_REPLY_TO_MESSAGE, this.fetchAndSetReplyTo);
|
|
||||||
},
|
|
||||||
beforeDestroy() {
|
|
||||||
bus.$off(
|
|
||||||
BUS_EVENTS.NEW_CONVERSATION_MODAL,
|
|
||||||
this.onNewConversationModalActive
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
toggleRichContentEditor() {
|
toggleRichContentEditor() {
|
||||||
@@ -610,31 +587,6 @@ export default {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
saveDraft(conversationId, replyType) {
|
|
||||||
if (this.message || this.message === '') {
|
|
||||||
const key = `draft-${conversationId}-${replyType}`;
|
|
||||||
const draftToSave = trimContent(this.message || '');
|
|
||||||
|
|
||||||
this.$store.dispatch('draftMessages/set', {
|
|
||||||
key,
|
|
||||||
message: draftToSave,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setToDraft(conversationId, replyType) {
|
|
||||||
this.saveDraft(conversationId, replyType);
|
|
||||||
this.message = '';
|
|
||||||
},
|
|
||||||
getFromDraft() {
|
|
||||||
if (this.conversationIdByRoute) {
|
|
||||||
const key = `draft-${this.conversationIdByRoute}-${this.replyType}`;
|
|
||||||
const messageFromStore =
|
|
||||||
this.$store.getters['draftMessages/get'](key) || '';
|
|
||||||
|
|
||||||
// ensure that the message has signature set based on the ui setting
|
|
||||||
this.message = this.toggleSignatureForDraft(messageFromStore);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
toggleSignatureForDraft(message) {
|
toggleSignatureForDraft(message) {
|
||||||
if (this.isPrivate) {
|
if (this.isPrivate) {
|
||||||
return message;
|
return message;
|
||||||
@@ -644,12 +596,6 @@ export default {
|
|||||||
? appendSignature(message, this.signatureToApply)
|
? appendSignature(message, this.signatureToApply)
|
||||||
: removeSignature(message, this.signatureToApply);
|
: removeSignature(message, this.signatureToApply);
|
||||||
},
|
},
|
||||||
removeFromDraft() {
|
|
||||||
if (this.conversationIdByRoute) {
|
|
||||||
const key = `draft-${this.conversationIdByRoute}-${this.replyType}`;
|
|
||||||
this.$store.dispatch('draftMessages/delete', { key });
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleKeyEvents(e) {
|
handleKeyEvents(e) {
|
||||||
const keyCode = buildHotKeys(e);
|
const keyCode = buildHotKeys(e);
|
||||||
if (keyCode === 'escape') {
|
if (keyCode === 'escape') {
|
||||||
@@ -768,7 +714,6 @@ export default {
|
|||||||
: this.$track(CONVERSATION_EVENTS.SENT_MESSAGE, {
|
: this.$track(CONVERSATION_EVENTS.SENT_MESSAGE, {
|
||||||
channelType: this.channelType,
|
channelType: this.channelType,
|
||||||
signatureEnabled: this.sendWithSignature,
|
signatureEnabled: this.sendWithSignature,
|
||||||
hasReplyTo: !!this.inReplyTo?.id,
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
async onSendReply() {
|
async onSendReply() {
|
||||||
@@ -871,34 +816,10 @@ export default {
|
|||||||
}
|
}
|
||||||
this.attachedFiles = [];
|
this.attachedFiles = [];
|
||||||
this.isRecordingAudio = false;
|
this.isRecordingAudio = false;
|
||||||
this.resetReplyToMessage();
|
|
||||||
},
|
|
||||||
clearEmailField() {
|
|
||||||
this.ccEmails = '';
|
|
||||||
this.bccEmails = '';
|
|
||||||
this.toEmails = '';
|
|
||||||
},
|
},
|
||||||
toggleEmojiPicker() {
|
toggleEmojiPicker() {
|
||||||
this.showEmojiPicker = !this.showEmojiPicker;
|
this.showEmojiPicker = !this.showEmojiPicker;
|
||||||
},
|
},
|
||||||
toggleAudioRecorder() {
|
|
||||||
this.isRecordingAudio = !this.isRecordingAudio;
|
|
||||||
this.isRecorderAudioStopped = !this.isRecordingAudio;
|
|
||||||
if (!this.isRecordingAudio) {
|
|
||||||
this.clearMessage();
|
|
||||||
this.clearEmailField();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
toggleAudioRecorderPlayPause() {
|
|
||||||
if (this.isRecordingAudio) {
|
|
||||||
if (!this.isRecorderAudioStopped) {
|
|
||||||
this.isRecorderAudioStopped = true;
|
|
||||||
this.$refs.audioRecorderInput.stopAudioRecording();
|
|
||||||
} else if (this.isRecorderAudioStopped) {
|
|
||||||
this.$refs.audioRecorderInput.playPause();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
hideEmojiPicker() {
|
hideEmojiPicker() {
|
||||||
if (this.showEmojiPicker) {
|
if (this.showEmojiPicker) {
|
||||||
this.toggleEmojiPicker();
|
this.toggleEmojiPicker();
|
||||||
@@ -920,18 +841,6 @@ export default {
|
|||||||
onFocus() {
|
onFocus() {
|
||||||
this.isFocused = true;
|
this.isFocused = true;
|
||||||
},
|
},
|
||||||
onStateProgressRecorderChanged(duration) {
|
|
||||||
this.recordingAudioDurationText = duration;
|
|
||||||
},
|
|
||||||
onStateRecorderChanged(state) {
|
|
||||||
this.recordingAudioState = state;
|
|
||||||
if (state && 'notallowederror'.includes(state)) {
|
|
||||||
this.toggleAudioRecorder();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
onFinishRecorder(file) {
|
|
||||||
return file && this.onFileUpload(file);
|
|
||||||
},
|
|
||||||
toggleTyping(status) {
|
toggleTyping(status) {
|
||||||
const conversationId = this.currentChat.id;
|
const conversationId = this.currentChat.id;
|
||||||
const isPrivate = this.isPrivate;
|
const isPrivate = this.isPrivate;
|
||||||
@@ -946,24 +855,6 @@ export default {
|
|||||||
isPrivate,
|
isPrivate,
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
attachFile({ blob, file }) {
|
|
||||||
const reader = new FileReader();
|
|
||||||
reader.readAsDataURL(file.file);
|
|
||||||
reader.onloadend = () => {
|
|
||||||
this.attachedFiles.push({
|
|
||||||
currentChatId: this.currentChat.id,
|
|
||||||
resource: blob || file,
|
|
||||||
isPrivate: this.isPrivate,
|
|
||||||
thumb: reader.result,
|
|
||||||
blobSignedId: blob ? blob.signed_id : undefined,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
},
|
|
||||||
removeAttachment(itemIndex) {
|
|
||||||
this.attachedFiles = this.attachedFiles.filter(
|
|
||||||
(item, index) => itemIndex !== index
|
|
||||||
);
|
|
||||||
},
|
|
||||||
getMessagePayloadForWhatsapp(message) {
|
getMessagePayloadForWhatsapp(message) {
|
||||||
const multipleMessagePayload = [];
|
const multipleMessagePayload = [];
|
||||||
|
|
||||||
@@ -1003,10 +894,8 @@ export default {
|
|||||||
sender: this.sender,
|
sender: this.sender,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (this.inReplyTo?.id) {
|
if (this.inReplyTo) {
|
||||||
messagePayload.contentAttributes = {
|
messagePayload.contentAttributes = { in_reply_to: this.inReplyTo };
|
||||||
in_reply_to: this.inReplyTo.id,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.attachedFiles && this.attachedFiles.length) {
|
if (this.attachedFiles && this.attachedFiles.length) {
|
||||||
@@ -1034,78 +923,6 @@ export default {
|
|||||||
|
|
||||||
return messagePayload;
|
return messagePayload;
|
||||||
},
|
},
|
||||||
setCcEmails(value) {
|
|
||||||
this.bccEmails = value.bccEmails;
|
|
||||||
this.ccEmails = value.ccEmails;
|
|
||||||
},
|
|
||||||
setCCAndToEmailsFromLastChat() {
|
|
||||||
if (!this.lastEmail) return;
|
|
||||||
|
|
||||||
const {
|
|
||||||
content_attributes: { email: emailAttributes = {} },
|
|
||||||
} = this.lastEmail;
|
|
||||||
|
|
||||||
// Retrieve the email of the current conversation's sender
|
|
||||||
const conversationContact = this.currentChat?.meta?.sender?.email || '';
|
|
||||||
let cc = emailAttributes.cc ? [...emailAttributes.cc] : [];
|
|
||||||
let to = [];
|
|
||||||
|
|
||||||
// there might be a situation where the current conversation will include a message from a third person,
|
|
||||||
// and the current conversation contact is in CC.
|
|
||||||
// This is an edge-case, reported here: CW-1511 [ONLY FOR INTERNAL REFERENCE]
|
|
||||||
// So we remove the current conversation contact's email from the CC list if present
|
|
||||||
if (cc.includes(conversationContact)) {
|
|
||||||
cc = cc.filter(email => email !== conversationContact);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If the last incoming message sender is different from the conversation contact, add them to the "to"
|
|
||||||
// and add the conversation contact to the CC
|
|
||||||
if (!emailAttributes.from.includes(conversationContact)) {
|
|
||||||
to.push(...emailAttributes.from);
|
|
||||||
cc.push(conversationContact);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove the conversation contact's email from the BCC list if present
|
|
||||||
let bcc = (emailAttributes.bcc || []).filter(
|
|
||||||
email => email !== conversationContact
|
|
||||||
);
|
|
||||||
|
|
||||||
// Ensure only unique email addresses are in the CC list
|
|
||||||
bcc = [...new Set(bcc)];
|
|
||||||
cc = [...new Set(cc)];
|
|
||||||
to = [...new Set(to)];
|
|
||||||
|
|
||||||
this.ccEmails = cc.join(', ');
|
|
||||||
this.bccEmails = bcc.join(', ');
|
|
||||||
this.toEmails = to.join(', ');
|
|
||||||
},
|
|
||||||
fetchAndSetReplyTo() {
|
|
||||||
const replyStorageKey = LOCAL_STORAGE_KEYS.MESSAGE_REPLY_TO;
|
|
||||||
const replyToMessageId = LocalStorage.getFromJsonStore(
|
|
||||||
replyStorageKey,
|
|
||||||
this.conversationId
|
|
||||||
);
|
|
||||||
|
|
||||||
this.inReplyTo = this.currentChat?.messages?.find(message => {
|
|
||||||
if (message.id === replyToMessageId) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
resetReplyToMessage() {
|
|
||||||
const replyStorageKey = LOCAL_STORAGE_KEYS.MESSAGE_REPLY_TO;
|
|
||||||
LocalStorage.deleteFromJsonStore(replyStorageKey, this.conversationId);
|
|
||||||
bus.$emit(BUS_EVENTS.TOGGLE_REPLY_TO_MESSAGE);
|
|
||||||
},
|
|
||||||
onNewConversationModalActive(isActive) {
|
|
||||||
// Issue is if the new conversation modal is open and we drag and drop the file
|
|
||||||
// then the file is not getting attached to the new conversation modal
|
|
||||||
// and it is getting attached to the current conversation reply box
|
|
||||||
// so to fix this we are removing the drag and drop event listener from the current conversation reply box
|
|
||||||
// When new conversation modal is open
|
|
||||||
this.newConversationModalActive = isActive;
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@@ -1123,13 +940,6 @@ export default {
|
|||||||
@apply bg-transparent py-0 px-4;
|
@apply bg-transparent py-0 px-4;
|
||||||
}
|
}
|
||||||
|
|
||||||
.reply-box {
|
|
||||||
@apply border-t border-slate-50 dark:border-slate-700 bg-white dark:bg-slate-900;
|
|
||||||
|
|
||||||
&.is-private {
|
|
||||||
@apply bg-yellow-50 dark:bg-yellow-200;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.send-button {
|
.send-button {
|
||||||
@apply mb-0;
|
@apply mb-0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="reply-editor bg-slate-50 dark:bg-slate-800 rounded-md py-1 pl-2 pr-1 text-xs tracking-wide mt-2 flex items-center gap-1.5 -mx-2"
|
|
||||||
>
|
|
||||||
<fluent-icon class="flex-shrink-0 icon" icon="arrow-reply" size="14" />
|
|
||||||
<div class="flex-grow gap-1 mt-px text-xs truncate">
|
|
||||||
{{ $t('CONVERSATION.REPLYBOX.REPLYING_TO') }}
|
|
||||||
<message-preview
|
|
||||||
:message="message"
|
|
||||||
:show-message-type="false"
|
|
||||||
:default-empty-message="$t('CONVERSATION.REPLY_MESSAGE_NOT_FOUND')"
|
|
||||||
class="inline"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<woot-button
|
|
||||||
v-tooltip="$t('CONVERSATION.REPLYBOX.DISMISS_REPLY')"
|
|
||||||
color-scheme="secondary"
|
|
||||||
icon="dismiss"
|
|
||||||
variant="clear"
|
|
||||||
size="tiny"
|
|
||||||
class="flex-shrink-0"
|
|
||||||
@click.stop="$emit('dismiss')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import MessagePreview from 'dashboard/components/widgets/conversation/MessagePreview.vue';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
components: { MessagePreview },
|
|
||||||
props: {
|
|
||||||
message: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="scss">
|
|
||||||
// TODO: Remove this
|
|
||||||
// override for dashboard/assets/scss/widgets/_reply-box.scss
|
|
||||||
.reply-editor {
|
|
||||||
.icon {
|
|
||||||
margin-right: 0px !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
+1
-1
@@ -33,7 +33,7 @@
|
|||||||
<woot-button variant="smooth" @click="$emit('resetTemplate')">
|
<woot-button variant="smooth" @click="$emit('resetTemplate')">
|
||||||
{{ $t('WHATSAPP_TEMPLATES.PARSER.GO_BACK_LABEL') }}
|
{{ $t('WHATSAPP_TEMPLATES.PARSER.GO_BACK_LABEL') }}
|
||||||
</woot-button>
|
</woot-button>
|
||||||
<woot-button type="button" @click="sendMessage">
|
<woot-button @click="sendMessage">
|
||||||
{{ $t('WHATSAPP_TEMPLATES.PARSER.SEND_MESSAGE_LABEL') }}
|
{{ $t('WHATSAPP_TEMPLATES.PARSER.SEND_MESSAGE_LABEL') }}
|
||||||
</woot-button>
|
</woot-button>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -57,6 +57,17 @@
|
|||||||
@mouseenter="isHovered = true"
|
@mouseenter="isHovered = true"
|
||||||
@mouseleave="isHovered = false"
|
@mouseleave="isHovered = false"
|
||||||
/>
|
/>
|
||||||
|
<button
|
||||||
|
v-if="isATweet && (isIncoming || isOutgoing) && sourceId"
|
||||||
|
@click="onTweetReply"
|
||||||
|
>
|
||||||
|
<fluent-icon
|
||||||
|
v-tooltip.top-start="$t('CHAT_LIST.REPLY_TO_TWEET')"
|
||||||
|
icon="arrow-reply"
|
||||||
|
class="action--icon cursor-pointer"
|
||||||
|
size="16"
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
<a
|
<a
|
||||||
v-if="isATweet && (isOutgoing || isIncoming) && linkToTweet"
|
v-if="isATweet && (isOutgoing || isIncoming) && linkToTweet"
|
||||||
:href="linkToTweet"
|
:href="linkToTweet"
|
||||||
@@ -66,7 +77,7 @@
|
|||||||
<fluent-icon
|
<fluent-icon
|
||||||
v-tooltip.top-start="$t('CHAT_LIST.VIEW_TWEET_IN_TWITTER')"
|
v-tooltip.top-start="$t('CHAT_LIST.VIEW_TWEET_IN_TWITTER')"
|
||||||
icon="open"
|
icon="open"
|
||||||
class="cursor-pointer action--icon"
|
class="action--icon cursor-pointer"
|
||||||
size="16"
|
size="16"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
@@ -75,6 +86,7 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { MESSAGE_TYPE, MESSAGE_STATUS } from 'shared/constants/messages';
|
import { MESSAGE_TYPE, MESSAGE_STATUS } from 'shared/constants/messages';
|
||||||
|
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||||
import { mapGetters } from 'vuex';
|
import { mapGetters } from 'vuex';
|
||||||
import timeMixin from '../../../../mixins/time';
|
import timeMixin from '../../../../mixins/time';
|
||||||
@@ -201,11 +213,7 @@ export default {
|
|||||||
return !!this.sourceId;
|
return !!this.sourceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (this.isAWhatsAppChannel) {
|
||||||
this.isAWhatsAppChannel ||
|
|
||||||
this.isATwilioChannel ||
|
|
||||||
this.isAFacebookInbox
|
|
||||||
) {
|
|
||||||
return this.sourceId && this.isSent;
|
return this.sourceId && this.isSent;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -215,13 +223,9 @@ export default {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isAWhatsAppChannel || this.isATwilioChannel) {
|
if (this.isAWhatsAppChannel) {
|
||||||
return this.sourceId && this.isDelivered;
|
return this.sourceId && this.isDelivered;
|
||||||
}
|
}
|
||||||
// We will consider messages as delivered for web widget inbox and API inbox if they are sent
|
|
||||||
if (this.isAWebWidgetInbox || this.isAPIInbox) {
|
|
||||||
return this.isSent;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
@@ -235,17 +239,18 @@ export default {
|
|||||||
return contactLastSeenAt >= this.createdAt;
|
return contactLastSeenAt >= this.createdAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (this.isAWhatsAppChannel) {
|
||||||
this.isAWhatsAppChannel ||
|
|
||||||
this.isATwilioChannel ||
|
|
||||||
this.isAFacebookInbox
|
|
||||||
) {
|
|
||||||
return this.sourceId && this.isRead;
|
return this.sourceId && this.isRead;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
methods: {
|
||||||
|
onTweetReply() {
|
||||||
|
bus.$emit(BUS_EVENTS.SET_TWEET_REPLY, this.id);
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div
|
|
||||||
class="px-2 py-1.5 -mx-2 rounded-sm min-w-[15rem] mb-2"
|
|
||||||
:class="{
|
|
||||||
'bg-slate-100 dark:bg-slate-600 dark:text-slate-50':
|
|
||||||
messageType === MESSAGE_TYPE.INCOMING,
|
|
||||||
'bg-woot-600 text-woot-50': messageType === MESSAGE_TYPE.OUTGOING,
|
|
||||||
}"
|
|
||||||
>
|
|
||||||
<message-preview
|
|
||||||
:message="message"
|
|
||||||
:show-message-type="false"
|
|
||||||
:default-empty-message="$t('CONVERSATION.REPLY_MESSAGE_NOT_FOUND')"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
import MessagePreview from 'dashboard/components/widgets/conversation/MessagePreview.vue';
|
|
||||||
import { MESSAGE_TYPE } from 'shared/constants/messages';
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'ReplyTo',
|
|
||||||
components: {
|
|
||||||
MessagePreview,
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
message: {
|
|
||||||
type: Object,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
messageType: {
|
|
||||||
type: Number,
|
|
||||||
required: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
data() {
|
|
||||||
return { MESSAGE_TYPE };
|
|
||||||
},
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
@@ -32,22 +32,3 @@ export const ARTICLE_EDITOR_MENU_OPTIONS = [
|
|||||||
'imageUpload',
|
'imageUpload',
|
||||||
'code',
|
'code',
|
||||||
];
|
];
|
||||||
|
|
||||||
export const MESSAGE_EDITOR_IMAGE_RESIZES = [
|
|
||||||
{
|
|
||||||
name: 'Small',
|
|
||||||
height: '24px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Medium',
|
|
||||||
height: '48px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Large',
|
|
||||||
height: '72px',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'Original Size',
|
|
||||||
height: 'auto',
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ export default {
|
|||||||
EXPANDED: 'expanded',
|
EXPANDED: 'expanded',
|
||||||
},
|
},
|
||||||
DOCS_URL: '//www.chatwoot.com/docs/product/',
|
DOCS_URL: '//www.chatwoot.com/docs/product/',
|
||||||
HELP_CENTER_DOCS_URL: '//www.chatwoot.com/docs/product/others/help-center',
|
|
||||||
TESTIMONIAL_URL: 'https://testimonials.cdn.chatwoot.com/content.json',
|
TESTIMONIAL_URL: 'https://testimonials.cdn.chatwoot.com/content.json',
|
||||||
SMALL_SCREEN_BREAKPOINT: 1024,
|
SMALL_SCREEN_BREAKPOINT: 1024,
|
||||||
AVAILABILITY_STATUS_KEYS: ['online', 'busy', 'offline'],
|
AVAILABILITY_STATUS_KEYS: ['online', 'busy', 'offline'],
|
||||||
|
|||||||
@@ -4,5 +4,4 @@ export const LOCAL_STORAGE_KEYS = {
|
|||||||
DRAFT_MESSAGES: 'draftMessages',
|
DRAFT_MESSAGES: 'draftMessages',
|
||||||
COLOR_SCHEME: 'color_scheme',
|
COLOR_SCHEME: 'color_scheme',
|
||||||
DISMISSED_LABEL_SUGGESTIONS: 'labelSuggestionsDismissed',
|
DISMISSED_LABEL_SUGGESTIONS: 'labelSuggestionsDismissed',
|
||||||
MESSAGE_REPLY_TO: 'messageReplyTo',
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -16,5 +16,4 @@ export const FEATURE_FLAGS = {
|
|||||||
TEAM_MANAGEMENT: 'team_management',
|
TEAM_MANAGEMENT: 'team_management',
|
||||||
VOICE_RECORDER: 'voice_recorder',
|
VOICE_RECORDER: 'voice_recorder',
|
||||||
AUDIT_LOGS: 'audit_logs',
|
AUDIT_LOGS: 'audit_logs',
|
||||||
MESSAGE_REPLY_TO: 'message_reply_to',
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -156,128 +156,3 @@ export function extractTextFromMarkdown(markdown) {
|
|||||||
.replace(/\n{2,}/g, '\n') // Remove multiple consecutive newlines (blank lines)
|
.replace(/\n{2,}/g, '\n') // Remove multiple consecutive newlines (blank lines)
|
||||||
.trim(); // Trim any extra space
|
.trim(); // Trim any extra space
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Scrolls the editor view into current cursor position
|
|
||||||
*
|
|
||||||
* @param {EditorView} view - The Prosemirror EditorView
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export const scrollCursorIntoView = view => {
|
|
||||||
// Get the current selection's head position (where the cursor is).
|
|
||||||
const pos = view.state.selection.head;
|
|
||||||
|
|
||||||
// Get the corresponding DOM node for that position.
|
|
||||||
const domAtPos = view.domAtPos(pos);
|
|
||||||
const node = domAtPos.node;
|
|
||||||
|
|
||||||
// Scroll the node into view.
|
|
||||||
if (node && node.scrollIntoView) {
|
|
||||||
node.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a transaction that inserts a node into editor at the given position
|
|
||||||
* Has an optional param 'content' to check if the
|
|
||||||
*
|
|
||||||
* @param {Node} node - The prosemirror node that needs to be inserted into the editor
|
|
||||||
* @param {number} from - Position in the editor where the node needs to be inserted
|
|
||||||
* @param {number} to - Position in the editor where the node needs to be replaced
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export function insertAtCursor(editorView, node, from, to) {
|
|
||||||
if (!editorView) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is a workaround to prevent inserting content into new line rather than on the exiting line
|
|
||||||
// If the node is of type 'doc' and has only one child which is a paragraph,
|
|
||||||
// then extract its inline content to be inserted as inline.
|
|
||||||
const isWrappedInParagraph =
|
|
||||||
node.type.name === 'doc' &&
|
|
||||||
node.childCount === 1 &&
|
|
||||||
node.firstChild.type.name === 'paragraph';
|
|
||||||
|
|
||||||
if (isWrappedInParagraph) {
|
|
||||||
node = node.firstChild.content;
|
|
||||||
}
|
|
||||||
|
|
||||||
let tr;
|
|
||||||
if (to) {
|
|
||||||
tr = editorView.state.tr.replaceWith(from, to, node).insertText(` `);
|
|
||||||
} else {
|
|
||||||
tr = editorView.state.tr.insert(from, node);
|
|
||||||
}
|
|
||||||
const state = editorView.state.apply(tr);
|
|
||||||
editorView.updateState(state);
|
|
||||||
editorView.focus();
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Determines the appropriate node and position to insert an image in the editor.
|
|
||||||
*
|
|
||||||
* Based on the current editor state and the provided image URL, this function finds out the correct node (either
|
|
||||||
* a standalone image node or an image wrapped in a paragraph) and its respective position in the editor.
|
|
||||||
*
|
|
||||||
* 1. If the current node is a paragraph and doesn't contain an image or text, the image is inserted directly into it.
|
|
||||||
* 2. If the current node isn't a paragraph or it's a paragraph containing text, the image will be wrapped
|
|
||||||
* in a new paragraph and then inserted.
|
|
||||||
* 3. If the current node is a paragraph containing an image, the new image will be inserted directly into it.
|
|
||||||
*
|
|
||||||
* @param {Object} editorState - The current state of the editor. It provides necessary details like selection, schema, etc.
|
|
||||||
* @param {string} fileUrl - The URL of the image to be inserted into the editor.
|
|
||||||
* @returns {Object|null} An object containing details about the node to be inserted and its position. It returns null if no image node can be created.
|
|
||||||
* @property {Node} node - The ProseMirror node to be inserted (either an image node or a paragraph containing the image).
|
|
||||||
* @property {number} pos - The position where the new node should be inserted in the editor.
|
|
||||||
*/
|
|
||||||
|
|
||||||
export const findNodeToInsertImage = (editorState, fileUrl) => {
|
|
||||||
const { selection, schema } = editorState;
|
|
||||||
const { nodes } = schema;
|
|
||||||
const currentNode = selection.$from.node();
|
|
||||||
const {
|
|
||||||
type: { name: typeName },
|
|
||||||
content: { size, content },
|
|
||||||
} = currentNode;
|
|
||||||
|
|
||||||
const imageNode = nodes.image.create({ src: fileUrl });
|
|
||||||
|
|
||||||
if (!imageNode) return null;
|
|
||||||
|
|
||||||
const isInParagraph = typeName === 'paragraph';
|
|
||||||
const needsNewLine =
|
|
||||||
!content.some(n => n.type.name === 'image') && size !== 0 ? 1 : 0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
node: isInParagraph ? imageNode : nodes.paragraph.create({}, imageNode),
|
|
||||||
pos: selection.from + needsNewLine,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set URL with query and size.
|
|
||||||
*
|
|
||||||
* @param {Object} selectedImageNode - The current selected node.
|
|
||||||
* @param {Object} size - The size to set.
|
|
||||||
* @param {Object} editorView - The editor view.
|
|
||||||
*/
|
|
||||||
export function setURLWithQueryAndSize(selectedImageNode, size, editorView) {
|
|
||||||
if (selectedImageNode) {
|
|
||||||
// Create and apply the transaction
|
|
||||||
const tr = editorView.state.tr.setNodeMarkup(
|
|
||||||
editorView.state.selection.from,
|
|
||||||
null,
|
|
||||||
{
|
|
||||||
src: selectedImageNode.src,
|
|
||||||
height: size.height,
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
if (tr.docChanged) {
|
|
||||||
editorView.dispatch(tr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/**
|
||||||
|
* Determines the appropriate node and position to insert an image in the editor.
|
||||||
|
*
|
||||||
|
* Based on the current editor state and the provided image URL, this function finds out the correct node (either
|
||||||
|
* a standalone image node or an image wrapped in a paragraph) and its respective position in the editor.
|
||||||
|
*
|
||||||
|
* 1. If the current node is a paragraph and doesn't contain an image or text, the image is inserted directly into it.
|
||||||
|
* 2. If the current node isn't a paragraph or it's a paragraph containing text, the image will be wrapped
|
||||||
|
* in a new paragraph and then inserted.
|
||||||
|
* 3. If the current node is a paragraph containing an image, the new image will be inserted directly into it.
|
||||||
|
*
|
||||||
|
* @param {Object} editorState - The current state of the editor. It provides necessary details like selection, schema, etc.
|
||||||
|
* @param {string} fileUrl - The URL of the image to be inserted into the editor.
|
||||||
|
* @returns {Object|null} An object containing details about the node to be inserted and its position. It returns null if no image node can be created.
|
||||||
|
* @property {Node} node - The ProseMirror node to be inserted (either an image node or a paragraph containing the image).
|
||||||
|
* @property {number} pos - The position where the new node should be inserted in the editor.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const findNodeToInsertImage = (editorState, fileUrl) => {
|
||||||
|
const { selection, schema } = editorState;
|
||||||
|
const { nodes } = schema;
|
||||||
|
const currentNode = selection.$from.node();
|
||||||
|
const {
|
||||||
|
type: { name: typeName },
|
||||||
|
content: { size, content },
|
||||||
|
} = currentNode;
|
||||||
|
|
||||||
|
const imageNode = nodes.image.create({ src: fileUrl });
|
||||||
|
|
||||||
|
if (!imageNode) return null;
|
||||||
|
|
||||||
|
const isInParagraph = typeName === 'paragraph';
|
||||||
|
const needsNewLine =
|
||||||
|
!content.some(n => n.type.name === 'image') && size !== 0 ? 1 : 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
node: isInParagraph ? imageNode : nodes.paragraph.create({}, imageNode),
|
||||||
|
pos: selection.from + needsNewLine,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -5,43 +5,7 @@ import {
|
|||||||
replaceSignature,
|
replaceSignature,
|
||||||
cleanSignature,
|
cleanSignature,
|
||||||
extractTextFromMarkdown,
|
extractTextFromMarkdown,
|
||||||
insertAtCursor,
|
|
||||||
findNodeToInsertImage,
|
|
||||||
setURLWithQueryAndSize,
|
|
||||||
} from '../editorHelper';
|
} from '../editorHelper';
|
||||||
import { EditorState } from 'prosemirror-state';
|
|
||||||
import { EditorView } from 'prosemirror-view';
|
|
||||||
import { Schema } from 'prosemirror-model';
|
|
||||||
|
|
||||||
// Define a basic ProseMirror schema
|
|
||||||
const schema = new Schema({
|
|
||||||
nodes: {
|
|
||||||
doc: { content: 'paragraph+' },
|
|
||||||
paragraph: {
|
|
||||||
content: 'text*',
|
|
||||||
toDOM: () => ['p', 0], // Represents a paragraph as a <p> tag in the DOM.
|
|
||||||
},
|
|
||||||
text: {
|
|
||||||
toDOM: node => node.text, // Represents text as its actual string value.
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize a basic EditorState for testing
|
|
||||||
const createEditorState = (content = '') => {
|
|
||||||
if (!content) {
|
|
||||||
return EditorState.create({
|
|
||||||
schema,
|
|
||||||
doc: schema.node('doc', null, [schema.node('paragraph')]),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return EditorState.create({
|
|
||||||
schema,
|
|
||||||
doc: schema.node('doc', null, [
|
|
||||||
schema.node('paragraph', null, [schema.text(content)]),
|
|
||||||
]),
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const NEW_SIGNATURE = 'This is a new signature';
|
const NEW_SIGNATURE = 'This is a new signature';
|
||||||
|
|
||||||
@@ -234,208 +198,3 @@ describe('extractTextFromMarkdown', () => {
|
|||||||
expect(extractTextFromMarkdown(markdown)).toEqual(expected);
|
expect(extractTextFromMarkdown(markdown)).toEqual(expected);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('insertAtCursor', () => {
|
|
||||||
it('should return undefined if editorView is not provided', () => {
|
|
||||||
const result = insertAtCursor(undefined, schema.text('Hello'), 0);
|
|
||||||
expect(result).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should unwrap doc nodes that are wrapped in a paragraph', () => {
|
|
||||||
const docNode = schema.node('doc', null, [
|
|
||||||
schema.node('paragraph', null, [schema.text('Hello')]),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const editorState = createEditorState();
|
|
||||||
const editorView = new EditorView(document.body, { state: editorState });
|
|
||||||
|
|
||||||
insertAtCursor(editorView, docNode, 0);
|
|
||||||
|
|
||||||
// Check if node was unwrapped and inserted correctly
|
|
||||||
expect(editorView.state.doc.firstChild.firstChild.text).toBe('Hello');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should insert node without replacing any content if "to" is not provided', () => {
|
|
||||||
const editorState = createEditorState();
|
|
||||||
const editorView = new EditorView(document.body, { state: editorState });
|
|
||||||
|
|
||||||
insertAtCursor(editorView, schema.text('Hello'), 0);
|
|
||||||
|
|
||||||
// Check if node was inserted correctly
|
|
||||||
expect(editorView.state.doc.firstChild.firstChild.text).toBe('Hello');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should replace content between "from" and "to" with the provided node', () => {
|
|
||||||
const editorState = createEditorState('ReplaceMe');
|
|
||||||
const editorView = new EditorView(document.body, { state: editorState });
|
|
||||||
|
|
||||||
insertAtCursor(editorView, schema.text('Hello'), 0, 8);
|
|
||||||
|
|
||||||
// Check if content was replaced correctly
|
|
||||||
expect(editorView.state.doc.firstChild.firstChild.text).toBe('Hello Me');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('findNodeToInsertImage', () => {
|
|
||||||
let mockEditorState;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
mockEditorState = {
|
|
||||||
selection: {
|
|
||||||
$from: {
|
|
||||||
node: jest.fn(() => ({})),
|
|
||||||
},
|
|
||||||
from: 0,
|
|
||||||
},
|
|
||||||
schema: {
|
|
||||||
nodes: {
|
|
||||||
image: {
|
|
||||||
create: jest.fn(attrs => ({ type: { name: 'image' }, attrs })),
|
|
||||||
},
|
|
||||||
paragraph: {
|
|
||||||
create: jest.fn((_, node) => ({
|
|
||||||
type: { name: 'paragraph' },
|
|
||||||
content: [node],
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should insert image directly into an empty paragraph', () => {
|
|
||||||
const mockNode = {
|
|
||||||
type: { name: 'paragraph' },
|
|
||||||
content: { size: 0, content: [] },
|
|
||||||
};
|
|
||||||
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
|
||||||
|
|
||||||
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
|
||||||
expect(result).toEqual({
|
|
||||||
node: { type: { name: 'image' }, attrs: { src: 'image-url' } },
|
|
||||||
pos: 0,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should insert image directly into a paragraph without an image but with other content', () => {
|
|
||||||
const mockNode = {
|
|
||||||
type: { name: 'paragraph' },
|
|
||||||
content: {
|
|
||||||
size: 1,
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: { name: 'text' },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
|
||||||
mockEditorState.selection.from = 1;
|
|
||||||
|
|
||||||
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
|
||||||
expect(result).toEqual({
|
|
||||||
node: { type: { name: 'image' }, attrs: { src: 'image-url' } },
|
|
||||||
pos: 2, // Because it should insert after the text, on a new line.
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it("should wrap image in a new paragraph when the current node isn't a paragraph", () => {
|
|
||||||
const mockNode = {
|
|
||||||
type: { name: 'not-a-paragraph' },
|
|
||||||
content: { size: 0, content: [] },
|
|
||||||
};
|
|
||||||
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
|
||||||
|
|
||||||
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
|
||||||
expect(result.node.type.name).toBe('paragraph');
|
|
||||||
expect(result.node.content[0].type.name).toBe('image');
|
|
||||||
expect(result.node.content[0].attrs.src).toBe('image-url');
|
|
||||||
expect(result.pos).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should insert a new image directly into the paragraph that already contains an image', () => {
|
|
||||||
const mockNode = {
|
|
||||||
type: { name: 'paragraph' },
|
|
||||||
content: {
|
|
||||||
size: 1,
|
|
||||||
content: [
|
|
||||||
{
|
|
||||||
type: { name: 'image', attrs: { src: 'existing-image-url' } },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
};
|
|
||||||
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
|
||||||
mockEditorState.selection.from = 1;
|
|
||||||
|
|
||||||
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
|
||||||
expect(result.node.type.name).toBe('image');
|
|
||||||
expect(result.node.attrs.src).toBe('image-url');
|
|
||||||
expect(result.pos).toBe(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('setURLWithQueryAndSize', () => {
|
|
||||||
let selectedNode;
|
|
||||||
let editorView;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
selectedNode = {
|
|
||||||
setAttribute: jest.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
const tr = {
|
|
||||||
setNodeMarkup: jest.fn().mockReturnValue({
|
|
||||||
docChanged: true,
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
selection: { from: 0 },
|
|
||||||
tr,
|
|
||||||
};
|
|
||||||
|
|
||||||
editorView = {
|
|
||||||
state,
|
|
||||||
dispatch: jest.fn(),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
it('updates the URL with the given size and updates the editor view', () => {
|
|
||||||
const size = { height: '20px' };
|
|
||||||
|
|
||||||
setURLWithQueryAndSize(selectedNode, size, editorView);
|
|
||||||
|
|
||||||
// Check if the editor view is updated
|
|
||||||
expect(editorView.dispatch).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('updates the URL with the given size and updates the editor view with original size', () => {
|
|
||||||
const size = { height: 'auto' };
|
|
||||||
|
|
||||||
setURLWithQueryAndSize(selectedNode, size, editorView);
|
|
||||||
|
|
||||||
// Check if the editor view is updated
|
|
||||||
expect(editorView.dispatch).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not update the editor view if the document has not changed', () => {
|
|
||||||
editorView.state.tr.setNodeMarkup = jest.fn().mockReturnValue({
|
|
||||||
docChanged: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const size = { height: '20px' };
|
|
||||||
|
|
||||||
setURLWithQueryAndSize(selectedNode, size, editorView);
|
|
||||||
|
|
||||||
// Check if the editor view dispatch was not called
|
|
||||||
expect(editorView.dispatch).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('does not perform any operations if selectedNode is not provided', () => {
|
|
||||||
setURLWithQueryAndSize(null, { height: '20px' }, editorView);
|
|
||||||
|
|
||||||
// Ensure the dispatch method wasn't called
|
|
||||||
expect(editorView.dispatch).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import { findNodeToInsertImage } from '../messageEditorHelper';
|
||||||
|
|
||||||
|
describe('findNodeToInsertImage', () => {
|
||||||
|
let mockEditorState;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
mockEditorState = {
|
||||||
|
selection: {
|
||||||
|
$from: {
|
||||||
|
node: jest.fn(() => ({})),
|
||||||
|
},
|
||||||
|
from: 0,
|
||||||
|
},
|
||||||
|
schema: {
|
||||||
|
nodes: {
|
||||||
|
image: {
|
||||||
|
create: jest.fn(attrs => ({ type: { name: 'image' }, attrs })),
|
||||||
|
},
|
||||||
|
paragraph: {
|
||||||
|
create: jest.fn((_, node) => ({
|
||||||
|
type: { name: 'paragraph' },
|
||||||
|
content: [node],
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should insert image directly into an empty paragraph', () => {
|
||||||
|
const mockNode = {
|
||||||
|
type: { name: 'paragraph' },
|
||||||
|
content: { size: 0, content: [] },
|
||||||
|
};
|
||||||
|
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
||||||
|
|
||||||
|
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
||||||
|
expect(result).toEqual({
|
||||||
|
node: { type: { name: 'image' }, attrs: { src: 'image-url' } },
|
||||||
|
pos: 0,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should insert image directly into a paragraph without an image but with other content', () => {
|
||||||
|
const mockNode = {
|
||||||
|
type: { name: 'paragraph' },
|
||||||
|
content: {
|
||||||
|
size: 1,
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: { name: 'text' },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
||||||
|
mockEditorState.selection.from = 1;
|
||||||
|
|
||||||
|
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
||||||
|
expect(result).toEqual({
|
||||||
|
node: { type: { name: 'image' }, attrs: { src: 'image-url' } },
|
||||||
|
pos: 2, // Because it should insert after the text, on a new line.
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should wrap image in a new paragraph when the current node isn't a paragraph", () => {
|
||||||
|
const mockNode = {
|
||||||
|
type: { name: 'not-a-paragraph' },
|
||||||
|
content: { size: 0, content: [] },
|
||||||
|
};
|
||||||
|
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
||||||
|
|
||||||
|
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
||||||
|
expect(result.node.type.name).toBe('paragraph');
|
||||||
|
expect(result.node.content[0].type.name).toBe('image');
|
||||||
|
expect(result.node.content[0].attrs.src).toBe('image-url');
|
||||||
|
expect(result.pos).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should insert a new image directly into the paragraph that already contains an image', () => {
|
||||||
|
const mockNode = {
|
||||||
|
type: { name: 'paragraph' },
|
||||||
|
content: {
|
||||||
|
size: 1,
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: { name: 'image', attrs: { src: 'existing-image-url' } },
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
mockEditorState.selection.$from.node.mockReturnValueOnce(mockNode);
|
||||||
|
mockEditorState.selection.from = 1;
|
||||||
|
|
||||||
|
const result = findNodeToInsertImage(mockEditorState, 'image-url');
|
||||||
|
expect(result.node.type.name).toBe('image');
|
||||||
|
expect(result.node.attrs.src).toBe('image-url');
|
||||||
|
expect(result.pos).toBe(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -41,16 +41,6 @@
|
|||||||
"STATUS": "Status",
|
"STATUS": "Status",
|
||||||
"ORDER_BY": "Order by"
|
"ORDER_BY": "Order by"
|
||||||
},
|
},
|
||||||
"CHAT_TIME_STAMP": {
|
|
||||||
"CREATED": {
|
|
||||||
"LATEST": "Created",
|
|
||||||
"OLDEST": "Created at:"
|
|
||||||
},
|
|
||||||
"LAST_ACTIVITY": {
|
|
||||||
"NOT_ACTIVE": "Last activity:",
|
|
||||||
"ACTIVE": "Last activity"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"CHAT_SORT_FILTER_ITEMS": {
|
"CHAT_SORT_FILTER_ITEMS": {
|
||||||
"latest": {
|
"latest": {
|
||||||
"TEXT": "Last activity"
|
"TEXT": "Last activity"
|
||||||
|
|||||||
@@ -204,10 +204,6 @@
|
|||||||
"PLACEHOLDER": "Write your message here",
|
"PLACEHOLDER": "Write your message here",
|
||||||
"ERROR": "Message can't be empty"
|
"ERROR": "Message can't be empty"
|
||||||
},
|
},
|
||||||
"ATTACHMENTS": {
|
|
||||||
"SELECT": "Choose files",
|
|
||||||
"HELP_TEXT": "Drag and drop files here or choose files to attach"
|
|
||||||
},
|
|
||||||
"SUBMIT": "Send message",
|
"SUBMIT": "Send message",
|
||||||
"CANCEL": "Cancel",
|
"CANCEL": "Cancel",
|
||||||
"SUCCESS_MESSAGE": "Message sent!",
|
"SUCCESS_MESSAGE": "Message sent!",
|
||||||
@@ -347,17 +343,17 @@
|
|||||||
"DESCRIPTION": "Merge contacts to combine two profiles into one, including all attributes and conversations. In case of conflict, the Primary contact’s attributes will take precedence.",
|
"DESCRIPTION": "Merge contacts to combine two profiles into one, including all attributes and conversations. In case of conflict, the Primary contact’s attributes will take precedence.",
|
||||||
"PRIMARY": {
|
"PRIMARY": {
|
||||||
"TITLE": "Primary contact",
|
"TITLE": "Primary contact",
|
||||||
"HELP_LABEL": "To be deleted"
|
"HELP_LABEL": "To be kept"
|
||||||
},
|
},
|
||||||
"PARENT": {
|
"CHILD": {
|
||||||
"TITLE": "Contact to merge",
|
"TITLE": "Contact to merge",
|
||||||
"PLACEHOLDER": "Search for a contact",
|
"PLACEHOLDER": "Search for a contact",
|
||||||
"HELP_LABEL": "To be kept"
|
"HELP_LABEL": "To be deleted"
|
||||||
},
|
},
|
||||||
"SUMMARY": {
|
"SUMMARY": {
|
||||||
"TITLE": "Summary",
|
"TITLE": "Summary",
|
||||||
"DELETE_WARNING": "Contact of <strong>%{primaryContactName}</strong> will be deleted.",
|
"DELETE_WARNING": "Contact of <strong>%{childContactName}</strong> will be deleted.",
|
||||||
"ATTRIBUTE_WARNING": "Contact details of <strong>%{primaryContactName}</strong> will be copied to <strong>%{parentContactName}</strong>."
|
"ATTRIBUTE_WARNING": "Contact details of <strong>%{childContactName}</strong> will be copied to <strong>%{primaryContactName}</strong>."
|
||||||
},
|
},
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"ERROR": "ERROR_MESSAGE"
|
"ERROR": "ERROR_MESSAGE"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
"ASSIGN_TO_ME": "Assign to me",
|
"ASSIGN_TO_ME": "Assign to me",
|
||||||
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
|
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
|
||||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 hour message window restriction",
|
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 hour message window restriction",
|
||||||
|
"SELECT_A_TWEET_TO_REPLY": "Please select a tweet to reply to.",
|
||||||
"REPLYING_TO": "You are replying to:",
|
"REPLYING_TO": "You are replying to:",
|
||||||
"REMOVE_SELECTION": "Remove Selection",
|
"REMOVE_SELECTION": "Remove Selection",
|
||||||
"DOWNLOAD": "Download",
|
"DOWNLOAD": "Download",
|
||||||
@@ -46,7 +47,6 @@
|
|||||||
"NO_RESPONSE": "No response",
|
"NO_RESPONSE": "No response",
|
||||||
"RATING_TITLE": "Rating",
|
"RATING_TITLE": "Rating",
|
||||||
"FEEDBACK_TITLE": "Feedback",
|
"FEEDBACK_TITLE": "Feedback",
|
||||||
"REPLY_MESSAGE_NOT_FOUND": "Message not available",
|
|
||||||
"CARD": {
|
"CARD": {
|
||||||
"SHOW_LABELS": "Show labels",
|
"SHOW_LABELS": "Show labels",
|
||||||
"HIDE_LABELS": "Hide labels"
|
"HIDE_LABELS": "Hide labels"
|
||||||
@@ -139,8 +139,7 @@
|
|||||||
"PRIVATE_NOTE": "Private Note",
|
"PRIVATE_NOTE": "Private Note",
|
||||||
"SEND": "Send",
|
"SEND": "Send",
|
||||||
"CREATE": "Add Note",
|
"CREATE": "Add Note",
|
||||||
"DISMISS_REPLY": "Dismiss reply",
|
"TWEET": "Tweet",
|
||||||
"REPLYING_TO": "Replying to:",
|
|
||||||
"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",
|
||||||
@@ -195,7 +194,6 @@
|
|||||||
},
|
},
|
||||||
"CONTEXT_MENU": {
|
"CONTEXT_MENU": {
|
||||||
"COPY": "Copy",
|
"COPY": "Copy",
|
||||||
"REPLY_TO": "Reply to this message",
|
|
||||||
"DELETE": "Delete",
|
"DELETE": "Delete",
|
||||||
"CREATE_A_CANNED_RESPONSE": "Add to canned responses",
|
"CREATE_A_CANNED_RESPONSE": "Add to canned responses",
|
||||||
"TRANSLATE": "Translate",
|
"TRANSLATE": "Translate",
|
||||||
|
|||||||
@@ -425,33 +425,6 @@
|
|||||||
"ERROR_MESSAGE": "Unable to delete category"
|
"ERROR_MESSAGE": "Unable to delete category"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"UPGRADE_PAGE": {
|
|
||||||
"TITLE": "Help Center",
|
|
||||||
"DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Upgrade your subscription to enable this feature.",
|
|
||||||
"SELF_HOSTED_DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Please contact your administrator to enable this feature.",
|
|
||||||
"BUTTON": {
|
|
||||||
"LEARN_MORE": "Learn more",
|
|
||||||
"UPGRADE": "Upgrade"
|
|
||||||
},
|
|
||||||
"FEATURES": {
|
|
||||||
"PORTALS": {
|
|
||||||
"TITLE": "Multiple portals",
|
|
||||||
"DESCRIPTION": "Create multiple help center portals for different products using the same account."
|
|
||||||
},
|
|
||||||
"LOCALES": {
|
|
||||||
"TITLE": "Full support for locales",
|
|
||||||
"DESCRIPTION": "Localize the portal in your language. We support all locales and allow translations for every article."
|
|
||||||
},
|
|
||||||
"SEO": {
|
|
||||||
"TITLE": "SEO-friendly design",
|
|
||||||
"DESCRIPTION": "Customize your meta tags to improve your visibility on search engines with our SEO-friendly pages."
|
|
||||||
},
|
|
||||||
"API": {
|
|
||||||
"TITLE": "Full API support",
|
|
||||||
"DESCRIPTION": "Use the portal as a headless CMS with third party front-end frameworks using our APIs."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,7 @@
|
|||||||
"REPORTS_OVERVIEW": "Overview",
|
"REPORTS_OVERVIEW": "Overview",
|
||||||
"FACEBOOK_REAUTHORIZE": "Your Facebook connection has expired, please reconnect your Facebook page to continue services",
|
"FACEBOOK_REAUTHORIZE": "Your Facebook connection has expired, please reconnect your Facebook page to continue services",
|
||||||
"HELP_CENTER": {
|
"HELP_CENTER": {
|
||||||
"TITLE": "Help Center",
|
"TITLE": "Help Center (Beta)",
|
||||||
"ALL_ARTICLES": "All Articles",
|
"ALL_ARTICLES": "All Articles",
|
||||||
"MY_ARTICLES": "My Articles",
|
"MY_ARTICLES": "My Articles",
|
||||||
"DRAFT": "Draft",
|
"DRAFT": "Draft",
|
||||||
|
|||||||
@@ -41,16 +41,6 @@
|
|||||||
"STATUS": "الحالة",
|
"STATUS": "الحالة",
|
||||||
"ORDER_BY": "الترتيب بواسطة"
|
"ORDER_BY": "الترتيب بواسطة"
|
||||||
},
|
},
|
||||||
"CHAT_TIME_STAMP": {
|
|
||||||
"CREATED": {
|
|
||||||
"LATEST": "Created",
|
|
||||||
"OLDEST": "تم إنشاؤها في:"
|
|
||||||
},
|
|
||||||
"LAST_ACTIVITY": {
|
|
||||||
"NOT_ACTIVE": "النشاط الأخير:",
|
|
||||||
"ACTIVE": "النشاط الأخير"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"CHAT_SORT_FILTER_ITEMS": {
|
"CHAT_SORT_FILTER_ITEMS": {
|
||||||
"latest": {
|
"latest": {
|
||||||
"TEXT": "النشاط الأخير"
|
"TEXT": "النشاط الأخير"
|
||||||
|
|||||||
@@ -204,10 +204,6 @@
|
|||||||
"PLACEHOLDER": "اكتب رسالتك هنا",
|
"PLACEHOLDER": "اكتب رسالتك هنا",
|
||||||
"ERROR": "لا يمكن أن تكون الرسالة فارغة"
|
"ERROR": "لا يمكن أن تكون الرسالة فارغة"
|
||||||
},
|
},
|
||||||
"ATTACHMENTS": {
|
|
||||||
"SELECT": "Choose files",
|
|
||||||
"HELP_TEXT": "Drag and drop files here or choose files to attach"
|
|
||||||
},
|
|
||||||
"SUBMIT": "إرسال الرسالة",
|
"SUBMIT": "إرسال الرسالة",
|
||||||
"CANCEL": "إلغاء",
|
"CANCEL": "إلغاء",
|
||||||
"SUCCESS_MESSAGE": "تم إرسال الرسالة!",
|
"SUCCESS_MESSAGE": "تم إرسال الرسالة!",
|
||||||
@@ -347,17 +343,17 @@
|
|||||||
"DESCRIPTION": "دمج جهات الاتصال للجمع بين ملفّين شخصيين في واحد، بما في ذلك جميع السمات والمحادثات. في حالة التعارض، تكون لخصائص جهة الاتصال الرئيسية الأسبقية.",
|
"DESCRIPTION": "دمج جهات الاتصال للجمع بين ملفّين شخصيين في واحد، بما في ذلك جميع السمات والمحادثات. في حالة التعارض، تكون لخصائص جهة الاتصال الرئيسية الأسبقية.",
|
||||||
"PRIMARY": {
|
"PRIMARY": {
|
||||||
"TITLE": "جهة الاتصال الرئيسية",
|
"TITLE": "جهة الاتصال الرئيسية",
|
||||||
"HELP_LABEL": "ليتم حذفها"
|
"HELP_LABEL": "سيتم الاحتفاظ بها"
|
||||||
},
|
},
|
||||||
"PARENT": {
|
"CHILD": {
|
||||||
"TITLE": "دمج جهة الإتصال",
|
"TITLE": "دمج جهة الإتصال",
|
||||||
"PLACEHOLDER": "بحث عن جهات الاتصال",
|
"PLACEHOLDER": "بحث عن جهات الاتصال",
|
||||||
"HELP_LABEL": "سيتم الاحتفاظ بها"
|
"HELP_LABEL": "ليتم حذفها"
|
||||||
},
|
},
|
||||||
"SUMMARY": {
|
"SUMMARY": {
|
||||||
"TITLE": "ملخص",
|
"TITLE": "ملخص",
|
||||||
"DELETE_WARNING": "سيتم حذف جهة الاتصال بـ <strong>%{primaryContactName}</strong>.",
|
"DELETE_WARNING": "سيتم حذف جهة الاتصال بـ <strong>%{childContactName}</strong>.",
|
||||||
"ATTRIBUTE_WARNING": "سيتم نسخ تفاصيل الاتصال بـ <strong>%{primaryContactName}</strong> إلى <strong>%{parentContactName}</strong>."
|
"ATTRIBUTE_WARNING": "سيتم نسخ تفاصيل الاتصال بـ <strong>%{childContactName}</strong> إلى <strong>%{primaryContactName}</strong>."
|
||||||
},
|
},
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"ERROR": "رسالة_خطأ"
|
"ERROR": "رسالة_خطأ"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
"ASSIGN_TO_ME": "إسناد لي",
|
"ASSIGN_TO_ME": "إسناد لي",
|
||||||
"TWILIO_WHATSAPP_CAN_REPLY": "يمكنك فقط الرد على هذه المحادثة باستخدام رسالة قالب بسبب",
|
"TWILIO_WHATSAPP_CAN_REPLY": "يمكنك فقط الرد على هذه المحادثة باستخدام رسالة قالب بسبب",
|
||||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "قيد نافذة الـ 24 ساعة",
|
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "قيد نافذة الـ 24 ساعة",
|
||||||
|
"SELECT_A_TWEET_TO_REPLY": "الرجاء تحديد تغريدة للرد عليها.",
|
||||||
"REPLYING_TO": "أنت ترد على:",
|
"REPLYING_TO": "أنت ترد على:",
|
||||||
"REMOVE_SELECTION": "إزالة التحديد",
|
"REMOVE_SELECTION": "إزالة التحديد",
|
||||||
"DOWNLOAD": "تنزيل",
|
"DOWNLOAD": "تنزيل",
|
||||||
@@ -46,7 +47,6 @@
|
|||||||
"NO_RESPONSE": "لا توجد استجابة",
|
"NO_RESPONSE": "لا توجد استجابة",
|
||||||
"RATING_TITLE": "التقييم",
|
"RATING_TITLE": "التقييم",
|
||||||
"FEEDBACK_TITLE": "الملاحظات",
|
"FEEDBACK_TITLE": "الملاحظات",
|
||||||
"REPLY_MESSAGE_NOT_FOUND": "Message not available",
|
|
||||||
"CARD": {
|
"CARD": {
|
||||||
"SHOW_LABELS": "إظهار السمات",
|
"SHOW_LABELS": "إظهار السمات",
|
||||||
"HIDE_LABELS": "إخفاء السمات"
|
"HIDE_LABELS": "إخفاء السمات"
|
||||||
@@ -139,8 +139,7 @@
|
|||||||
"PRIVATE_NOTE": "إضافة ملاحظة خاصة",
|
"PRIVATE_NOTE": "إضافة ملاحظة خاصة",
|
||||||
"SEND": "إرسال",
|
"SEND": "إرسال",
|
||||||
"CREATE": "إضافة ملاحظة",
|
"CREATE": "إضافة ملاحظة",
|
||||||
"DISMISS_REPLY": "Dismiss reply",
|
"TWEET": "تغريد",
|
||||||
"REPLYING_TO": "Replying to:",
|
|
||||||
"TIP_FORMAT_ICON": "عرض محرر النصوص",
|
"TIP_FORMAT_ICON": "عرض محرر النصوص",
|
||||||
"TIP_EMOJI_ICON": "إظهار قائمة الرموز التعبيرية",
|
"TIP_EMOJI_ICON": "إظهار قائمة الرموز التعبيرية",
|
||||||
"TIP_ATTACH_ICON": "إرفاق الملفات",
|
"TIP_ATTACH_ICON": "إرفاق الملفات",
|
||||||
@@ -195,7 +194,6 @@
|
|||||||
},
|
},
|
||||||
"CONTEXT_MENU": {
|
"CONTEXT_MENU": {
|
||||||
"COPY": "نسخ",
|
"COPY": "نسخ",
|
||||||
"REPLY_TO": "Reply to this message",
|
|
||||||
"DELETE": "حذف",
|
"DELETE": "حذف",
|
||||||
"CREATE_A_CANNED_RESPONSE": "إضافة إلى الردود السريعة",
|
"CREATE_A_CANNED_RESPONSE": "إضافة إلى الردود السريعة",
|
||||||
"TRANSLATE": "ترجم",
|
"TRANSLATE": "ترجم",
|
||||||
|
|||||||
@@ -425,33 +425,6 @@
|
|||||||
"ERROR_MESSAGE": "Unable to delete category"
|
"ERROR_MESSAGE": "Unable to delete category"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"UPGRADE_PAGE": {
|
|
||||||
"TITLE": "Help Center",
|
|
||||||
"DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Upgrade your subscription to enable this feature.",
|
|
||||||
"SELF_HOSTED_DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Please contact your administrator to enable this feature.",
|
|
||||||
"BUTTON": {
|
|
||||||
"LEARN_MORE": "اعرف المزيد",
|
|
||||||
"UPGRADE": "Upgrade"
|
|
||||||
},
|
|
||||||
"FEATURES": {
|
|
||||||
"PORTALS": {
|
|
||||||
"TITLE": "Multiple portals",
|
|
||||||
"DESCRIPTION": "Create multiple help center portals for different products using the same account."
|
|
||||||
},
|
|
||||||
"LOCALES": {
|
|
||||||
"TITLE": "Full support for locales",
|
|
||||||
"DESCRIPTION": "Localize the portal in your language. We support all locales and allow translations for every article."
|
|
||||||
},
|
|
||||||
"SEO": {
|
|
||||||
"TITLE": "SEO-friendly design",
|
|
||||||
"DESCRIPTION": "Customize your meta tags to improve your visibility on search engines with our SEO-friendly pages."
|
|
||||||
},
|
|
||||||
"API": {
|
|
||||||
"TITLE": "Full API support",
|
|
||||||
"DESCRIPTION": "Use the portal as a headless CMS with third party front-end frameworks using our APIs."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,7 @@
|
|||||||
"REPORTS_OVERVIEW": "نظرة عامة",
|
"REPORTS_OVERVIEW": "نظرة عامة",
|
||||||
"FACEBOOK_REAUTHORIZE": "انتهت صلاحية اتصال الفيسبوك الخاص بك، يرجى إعادة الاتصال بصفحة الفيسبوك الخاصة بك لمواصلة الخدمات",
|
"FACEBOOK_REAUTHORIZE": "انتهت صلاحية اتصال الفيسبوك الخاص بك، يرجى إعادة الاتصال بصفحة الفيسبوك الخاصة بك لمواصلة الخدمات",
|
||||||
"HELP_CENTER": {
|
"HELP_CENTER": {
|
||||||
"TITLE": "Help Center",
|
"TITLE": "مركز المساعدة (نسخة تجريبية)",
|
||||||
"ALL_ARTICLES": "جميع المقالات",
|
"ALL_ARTICLES": "جميع المقالات",
|
||||||
"MY_ARTICLES": "مقالاتي",
|
"MY_ARTICLES": "مقالاتي",
|
||||||
"DRAFT": "مسودة",
|
"DRAFT": "مسودة",
|
||||||
|
|||||||
@@ -41,16 +41,6 @@
|
|||||||
"STATUS": "Статус",
|
"STATUS": "Статус",
|
||||||
"ORDER_BY": "Order by"
|
"ORDER_BY": "Order by"
|
||||||
},
|
},
|
||||||
"CHAT_TIME_STAMP": {
|
|
||||||
"CREATED": {
|
|
||||||
"LATEST": "Created",
|
|
||||||
"OLDEST": "Създаден в:"
|
|
||||||
},
|
|
||||||
"LAST_ACTIVITY": {
|
|
||||||
"NOT_ACTIVE": "Last activity:",
|
|
||||||
"ACTIVE": "Last activity"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"CHAT_SORT_FILTER_ITEMS": {
|
"CHAT_SORT_FILTER_ITEMS": {
|
||||||
"latest": {
|
"latest": {
|
||||||
"TEXT": "Last activity"
|
"TEXT": "Last activity"
|
||||||
|
|||||||
@@ -204,10 +204,6 @@
|
|||||||
"PLACEHOLDER": "Напишете съобщението си тук",
|
"PLACEHOLDER": "Напишете съобщението си тук",
|
||||||
"ERROR": "Съобщението не може да е празно"
|
"ERROR": "Съобщението не може да е празно"
|
||||||
},
|
},
|
||||||
"ATTACHMENTS": {
|
|
||||||
"SELECT": "Choose files",
|
|
||||||
"HELP_TEXT": "Drag and drop files here or choose files to attach"
|
|
||||||
},
|
|
||||||
"SUBMIT": "Изпрати съобщение",
|
"SUBMIT": "Изпрати съобщение",
|
||||||
"CANCEL": "Отмени",
|
"CANCEL": "Отмени",
|
||||||
"SUCCESS_MESSAGE": "Съобщението е изпратено!",
|
"SUCCESS_MESSAGE": "Съобщението е изпратено!",
|
||||||
@@ -347,17 +343,17 @@
|
|||||||
"DESCRIPTION": "Обединете контактите, за да комбинирате два профила в един, включително всички атрибути и разговори. В случай на конфликт атрибутите на основния контакт ще имат предимство.",
|
"DESCRIPTION": "Обединете контактите, за да комбинирате два профила в един, включително всички атрибути и разговори. В случай на конфликт атрибутите на основния контакт ще имат предимство.",
|
||||||
"PRIMARY": {
|
"PRIMARY": {
|
||||||
"TITLE": "Основен контакт",
|
"TITLE": "Основен контакт",
|
||||||
"HELP_LABEL": "Да бъде изтрит"
|
"HELP_LABEL": "Да бъде запазено"
|
||||||
},
|
},
|
||||||
"PARENT": {
|
"CHILD": {
|
||||||
"TITLE": "Контакт за обединяване",
|
"TITLE": "Контакт за обединяване",
|
||||||
"PLACEHOLDER": "Търсене на контакт",
|
"PLACEHOLDER": "Търсене на контакт",
|
||||||
"HELP_LABEL": "Да бъде запазено"
|
"HELP_LABEL": "Да бъде изтрит"
|
||||||
},
|
},
|
||||||
"SUMMARY": {
|
"SUMMARY": {
|
||||||
"TITLE": "Резюме",
|
"TITLE": "Резюме",
|
||||||
"DELETE_WARNING": "Контакт от <strong>%{primaryContactName}</strong> ще бъде изтрит.",
|
"DELETE_WARNING": "Контакт от <strong>%{childContactName}</strong> ще бъде изтрит.",
|
||||||
"ATTRIBUTE_WARNING": "Детайлите на контакт <strong>%{primaryContactName}</strong> ще бъдат копирани в <strong>%{parentContactName}</strong>."
|
"ATTRIBUTE_WARNING": "Детайлите на контакт <strong>%{childContactName}</strong> ще бъдат копирани в <strong>%{primaryContactName}</strong>."
|
||||||
},
|
},
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"ERROR": "ГРЕШКА"
|
"ERROR": "ГРЕШКА"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
"ASSIGN_TO_ME": "Assign to me",
|
"ASSIGN_TO_ME": "Assign to me",
|
||||||
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
|
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
|
||||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 hour message window restriction",
|
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 hour message window restriction",
|
||||||
|
"SELECT_A_TWEET_TO_REPLY": "Please select a tweet to reply to.",
|
||||||
"REPLYING_TO": "You are replying to:",
|
"REPLYING_TO": "You are replying to:",
|
||||||
"REMOVE_SELECTION": "Remove Selection",
|
"REMOVE_SELECTION": "Remove Selection",
|
||||||
"DOWNLOAD": "Download",
|
"DOWNLOAD": "Download",
|
||||||
@@ -46,7 +47,6 @@
|
|||||||
"NO_RESPONSE": "No response",
|
"NO_RESPONSE": "No response",
|
||||||
"RATING_TITLE": "Rating",
|
"RATING_TITLE": "Rating",
|
||||||
"FEEDBACK_TITLE": "Feedback",
|
"FEEDBACK_TITLE": "Feedback",
|
||||||
"REPLY_MESSAGE_NOT_FOUND": "Message not available",
|
|
||||||
"CARD": {
|
"CARD": {
|
||||||
"SHOW_LABELS": "Show labels",
|
"SHOW_LABELS": "Show labels",
|
||||||
"HIDE_LABELS": "Hide labels"
|
"HIDE_LABELS": "Hide labels"
|
||||||
@@ -139,8 +139,7 @@
|
|||||||
"PRIVATE_NOTE": "Private Note",
|
"PRIVATE_NOTE": "Private Note",
|
||||||
"SEND": "Send",
|
"SEND": "Send",
|
||||||
"CREATE": "Add Note",
|
"CREATE": "Add Note",
|
||||||
"DISMISS_REPLY": "Dismiss reply",
|
"TWEET": "Tweet",
|
||||||
"REPLYING_TO": "Replying to:",
|
|
||||||
"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",
|
||||||
@@ -195,7 +194,6 @@
|
|||||||
},
|
},
|
||||||
"CONTEXT_MENU": {
|
"CONTEXT_MENU": {
|
||||||
"COPY": "Copy",
|
"COPY": "Copy",
|
||||||
"REPLY_TO": "Reply to this message",
|
|
||||||
"DELETE": "Изтрий",
|
"DELETE": "Изтрий",
|
||||||
"CREATE_A_CANNED_RESPONSE": "Add to canned responses",
|
"CREATE_A_CANNED_RESPONSE": "Add to canned responses",
|
||||||
"TRANSLATE": "Translate",
|
"TRANSLATE": "Translate",
|
||||||
|
|||||||
@@ -425,33 +425,6 @@
|
|||||||
"ERROR_MESSAGE": "Unable to delete category"
|
"ERROR_MESSAGE": "Unable to delete category"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"UPGRADE_PAGE": {
|
|
||||||
"TITLE": "Help Center",
|
|
||||||
"DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Upgrade your subscription to enable this feature.",
|
|
||||||
"SELF_HOSTED_DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Please contact your administrator to enable this feature.",
|
|
||||||
"BUTTON": {
|
|
||||||
"LEARN_MORE": "Learn more",
|
|
||||||
"UPGRADE": "Upgrade"
|
|
||||||
},
|
|
||||||
"FEATURES": {
|
|
||||||
"PORTALS": {
|
|
||||||
"TITLE": "Multiple portals",
|
|
||||||
"DESCRIPTION": "Create multiple help center portals for different products using the same account."
|
|
||||||
},
|
|
||||||
"LOCALES": {
|
|
||||||
"TITLE": "Full support for locales",
|
|
||||||
"DESCRIPTION": "Localize the portal in your language. We support all locales and allow translations for every article."
|
|
||||||
},
|
|
||||||
"SEO": {
|
|
||||||
"TITLE": "SEO-friendly design",
|
|
||||||
"DESCRIPTION": "Customize your meta tags to improve your visibility on search engines with our SEO-friendly pages."
|
|
||||||
},
|
|
||||||
"API": {
|
|
||||||
"TITLE": "Full API support",
|
|
||||||
"DESCRIPTION": "Use the portal as a headless CMS with third party front-end frameworks using our APIs."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,7 @@
|
|||||||
"REPORTS_OVERVIEW": "Overview",
|
"REPORTS_OVERVIEW": "Overview",
|
||||||
"FACEBOOK_REAUTHORIZE": "Your Facebook connection has expired, please reconnect your Facebook page to continue services",
|
"FACEBOOK_REAUTHORIZE": "Your Facebook connection has expired, please reconnect your Facebook page to continue services",
|
||||||
"HELP_CENTER": {
|
"HELP_CENTER": {
|
||||||
"TITLE": "Help Center",
|
"TITLE": "Help Center (Beta)",
|
||||||
"ALL_ARTICLES": "All Articles",
|
"ALL_ARTICLES": "All Articles",
|
||||||
"MY_ARTICLES": "My Articles",
|
"MY_ARTICLES": "My Articles",
|
||||||
"DRAFT": "Draft",
|
"DRAFT": "Draft",
|
||||||
|
|||||||
@@ -41,16 +41,6 @@
|
|||||||
"STATUS": "Estat",
|
"STATUS": "Estat",
|
||||||
"ORDER_BY": "Order by"
|
"ORDER_BY": "Order by"
|
||||||
},
|
},
|
||||||
"CHAT_TIME_STAMP": {
|
|
||||||
"CREATED": {
|
|
||||||
"LATEST": "Created",
|
|
||||||
"OLDEST": "Created at:"
|
|
||||||
},
|
|
||||||
"LAST_ACTIVITY": {
|
|
||||||
"NOT_ACTIVE": "Last activity:",
|
|
||||||
"ACTIVE": "Last activity"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"CHAT_SORT_FILTER_ITEMS": {
|
"CHAT_SORT_FILTER_ITEMS": {
|
||||||
"latest": {
|
"latest": {
|
||||||
"TEXT": "Last activity"
|
"TEXT": "Last activity"
|
||||||
|
|||||||
@@ -204,10 +204,6 @@
|
|||||||
"PLACEHOLDER": "Write your message here",
|
"PLACEHOLDER": "Write your message here",
|
||||||
"ERROR": "Message can't be empty"
|
"ERROR": "Message can't be empty"
|
||||||
},
|
},
|
||||||
"ATTACHMENTS": {
|
|
||||||
"SELECT": "Choose files",
|
|
||||||
"HELP_TEXT": "Drag and drop files here or choose files to attach"
|
|
||||||
},
|
|
||||||
"SUBMIT": "Send message",
|
"SUBMIT": "Send message",
|
||||||
"CANCEL": "Cancel·la",
|
"CANCEL": "Cancel·la",
|
||||||
"SUCCESS_MESSAGE": "Message sent!",
|
"SUCCESS_MESSAGE": "Message sent!",
|
||||||
@@ -347,17 +343,17 @@
|
|||||||
"DESCRIPTION": "Merge contacts to combine two profiles into one, including all attributes and conversations. In case of conflict, the Primary contact’ s attributes will take precedence.",
|
"DESCRIPTION": "Merge contacts to combine two profiles into one, including all attributes and conversations. In case of conflict, the Primary contact’ s attributes will take precedence.",
|
||||||
"PRIMARY": {
|
"PRIMARY": {
|
||||||
"TITLE": "Primary contact",
|
"TITLE": "Primary contact",
|
||||||
"HELP_LABEL": "To be deleted"
|
"HELP_LABEL": "To be kept"
|
||||||
},
|
},
|
||||||
"PARENT": {
|
"CHILD": {
|
||||||
"TITLE": "Contact to merge",
|
"TITLE": "Contact to merge",
|
||||||
"PLACEHOLDER": "Search for a contact",
|
"PLACEHOLDER": "Search for a contact",
|
||||||
"HELP_LABEL": "To be kept"
|
"HELP_LABEL": "To be deleted"
|
||||||
},
|
},
|
||||||
"SUMMARY": {
|
"SUMMARY": {
|
||||||
"TITLE": "Summary",
|
"TITLE": "Summary",
|
||||||
"DELETE_WARNING": "Contact of <strong>%{primaryContactName}</strong> will be deleted.",
|
"DELETE_WARNING": "Contact of <strong>%{childContactName}</strong> will be deleted.",
|
||||||
"ATTRIBUTE_WARNING": "Contact details of <strong>%{primaryContactName}</strong> will be copied to <strong>%{parentContactName}</strong>."
|
"ATTRIBUTE_WARNING": "Contact details of <strong>%{childContactName}</strong> will be copied to <strong>%{primaryContactName}</strong>."
|
||||||
},
|
},
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"ERROR": "ERROR_MESSAGE"
|
"ERROR": "ERROR_MESSAGE"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
"ASSIGN_TO_ME": "Assign to me",
|
"ASSIGN_TO_ME": "Assign to me",
|
||||||
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
|
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
|
||||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "Restricció de finestra de missatges de 24 hores",
|
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "Restricció de finestra de missatges de 24 hores",
|
||||||
|
"SELECT_A_TWEET_TO_REPLY": "Please select a tweet to reply to.",
|
||||||
"REPLYING_TO": "Estas responent a:",
|
"REPLYING_TO": "Estas responent a:",
|
||||||
"REMOVE_SELECTION": "Elimina la selecció",
|
"REMOVE_SELECTION": "Elimina la selecció",
|
||||||
"DOWNLOAD": "Descarrega",
|
"DOWNLOAD": "Descarrega",
|
||||||
@@ -46,7 +47,6 @@
|
|||||||
"NO_RESPONSE": "Sense resposta",
|
"NO_RESPONSE": "Sense resposta",
|
||||||
"RATING_TITLE": "Rating",
|
"RATING_TITLE": "Rating",
|
||||||
"FEEDBACK_TITLE": "Feedback",
|
"FEEDBACK_TITLE": "Feedback",
|
||||||
"REPLY_MESSAGE_NOT_FOUND": "Message not available",
|
|
||||||
"CARD": {
|
"CARD": {
|
||||||
"SHOW_LABELS": "Show labels",
|
"SHOW_LABELS": "Show labels",
|
||||||
"HIDE_LABELS": "Hide labels"
|
"HIDE_LABELS": "Hide labels"
|
||||||
@@ -139,8 +139,7 @@
|
|||||||
"PRIVATE_NOTE": "Nota privada",
|
"PRIVATE_NOTE": "Nota privada",
|
||||||
"SEND": "Envia",
|
"SEND": "Envia",
|
||||||
"CREATE": "Afegeix una nota",
|
"CREATE": "Afegeix una nota",
|
||||||
"DISMISS_REPLY": "Dismiss reply",
|
"TWEET": "Tuit",
|
||||||
"REPLYING_TO": "Replying to:",
|
|
||||||
"TIP_FORMAT_ICON": "Mostra l'editor de text enriquit",
|
"TIP_FORMAT_ICON": "Mostra l'editor de text enriquit",
|
||||||
"TIP_EMOJI_ICON": "Mostra la selecció d'emoticones",
|
"TIP_EMOJI_ICON": "Mostra la selecció d'emoticones",
|
||||||
"TIP_ATTACH_ICON": "Ajuntar fitxers",
|
"TIP_ATTACH_ICON": "Ajuntar fitxers",
|
||||||
@@ -195,7 +194,6 @@
|
|||||||
},
|
},
|
||||||
"CONTEXT_MENU": {
|
"CONTEXT_MENU": {
|
||||||
"COPY": "Copia",
|
"COPY": "Copia",
|
||||||
"REPLY_TO": "Reply to this message",
|
|
||||||
"DELETE": "Esborrar",
|
"DELETE": "Esborrar",
|
||||||
"CREATE_A_CANNED_RESPONSE": "Add to canned responses",
|
"CREATE_A_CANNED_RESPONSE": "Add to canned responses",
|
||||||
"TRANSLATE": "Translate",
|
"TRANSLATE": "Translate",
|
||||||
|
|||||||
@@ -425,33 +425,6 @@
|
|||||||
"ERROR_MESSAGE": "Unable to delete category"
|
"ERROR_MESSAGE": "Unable to delete category"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"UPGRADE_PAGE": {
|
|
||||||
"TITLE": "Help Center",
|
|
||||||
"DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Upgrade your subscription to enable this feature.",
|
|
||||||
"SELF_HOSTED_DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Please contact your administrator to enable this feature.",
|
|
||||||
"BUTTON": {
|
|
||||||
"LEARN_MORE": "Learn more",
|
|
||||||
"UPGRADE": "Upgrade"
|
|
||||||
},
|
|
||||||
"FEATURES": {
|
|
||||||
"PORTALS": {
|
|
||||||
"TITLE": "Multiple portals",
|
|
||||||
"DESCRIPTION": "Create multiple help center portals for different products using the same account."
|
|
||||||
},
|
|
||||||
"LOCALES": {
|
|
||||||
"TITLE": "Full support for locales",
|
|
||||||
"DESCRIPTION": "Localize the portal in your language. We support all locales and allow translations for every article."
|
|
||||||
},
|
|
||||||
"SEO": {
|
|
||||||
"TITLE": "SEO-friendly design",
|
|
||||||
"DESCRIPTION": "Customize your meta tags to improve your visibility on search engines with our SEO-friendly pages."
|
|
||||||
},
|
|
||||||
"API": {
|
|
||||||
"TITLE": "Full API support",
|
|
||||||
"DESCRIPTION": "Use the portal as a headless CMS with third party front-end frameworks using our APIs."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,7 @@
|
|||||||
"REPORTS_OVERVIEW": "Overview",
|
"REPORTS_OVERVIEW": "Overview",
|
||||||
"FACEBOOK_REAUTHORIZE": "La teva connexió a Facebook ha caducat, torna a connectar la vostra pàgina de Facebook per continuar els serveis",
|
"FACEBOOK_REAUTHORIZE": "La teva connexió a Facebook ha caducat, torna a connectar la vostra pàgina de Facebook per continuar els serveis",
|
||||||
"HELP_CENTER": {
|
"HELP_CENTER": {
|
||||||
"TITLE": "Help Center",
|
"TITLE": "Help Center (Beta)",
|
||||||
"ALL_ARTICLES": "All Articles",
|
"ALL_ARTICLES": "All Articles",
|
||||||
"MY_ARTICLES": "My Articles",
|
"MY_ARTICLES": "My Articles",
|
||||||
"DRAFT": "Draft",
|
"DRAFT": "Draft",
|
||||||
|
|||||||
@@ -41,16 +41,6 @@
|
|||||||
"STATUS": "Stav",
|
"STATUS": "Stav",
|
||||||
"ORDER_BY": "Order by"
|
"ORDER_BY": "Order by"
|
||||||
},
|
},
|
||||||
"CHAT_TIME_STAMP": {
|
|
||||||
"CREATED": {
|
|
||||||
"LATEST": "Created",
|
|
||||||
"OLDEST": "Created at:"
|
|
||||||
},
|
|
||||||
"LAST_ACTIVITY": {
|
|
||||||
"NOT_ACTIVE": "Last activity:",
|
|
||||||
"ACTIVE": "Last activity"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"CHAT_SORT_FILTER_ITEMS": {
|
"CHAT_SORT_FILTER_ITEMS": {
|
||||||
"latest": {
|
"latest": {
|
||||||
"TEXT": "Last activity"
|
"TEXT": "Last activity"
|
||||||
|
|||||||
@@ -204,10 +204,6 @@
|
|||||||
"PLACEHOLDER": "Write your message here",
|
"PLACEHOLDER": "Write your message here",
|
||||||
"ERROR": "Message can't be empty"
|
"ERROR": "Message can't be empty"
|
||||||
},
|
},
|
||||||
"ATTACHMENTS": {
|
|
||||||
"SELECT": "Choose files",
|
|
||||||
"HELP_TEXT": "Drag and drop files here or choose files to attach"
|
|
||||||
},
|
|
||||||
"SUBMIT": "Send message",
|
"SUBMIT": "Send message",
|
||||||
"CANCEL": "Zrušit",
|
"CANCEL": "Zrušit",
|
||||||
"SUCCESS_MESSAGE": "Message sent!",
|
"SUCCESS_MESSAGE": "Message sent!",
|
||||||
@@ -347,17 +343,17 @@
|
|||||||
"DESCRIPTION": "Merge contacts to combine two profiles into one, including all attributes and conversations. In case of conflict, the Primary contact’ s attributes will take precedence.",
|
"DESCRIPTION": "Merge contacts to combine two profiles into one, including all attributes and conversations. In case of conflict, the Primary contact’ s attributes will take precedence.",
|
||||||
"PRIMARY": {
|
"PRIMARY": {
|
||||||
"TITLE": "Primary contact",
|
"TITLE": "Primary contact",
|
||||||
"HELP_LABEL": "To be deleted"
|
"HELP_LABEL": "To be kept"
|
||||||
},
|
},
|
||||||
"PARENT": {
|
"CHILD": {
|
||||||
"TITLE": "Contact to merge",
|
"TITLE": "Contact to merge",
|
||||||
"PLACEHOLDER": "Search for a contact",
|
"PLACEHOLDER": "Search for a contact",
|
||||||
"HELP_LABEL": "To be kept"
|
"HELP_LABEL": "To be deleted"
|
||||||
},
|
},
|
||||||
"SUMMARY": {
|
"SUMMARY": {
|
||||||
"TITLE": "Summary",
|
"TITLE": "Summary",
|
||||||
"DELETE_WARNING": "Contact of <strong>%{primaryContactName}</strong> will be deleted.",
|
"DELETE_WARNING": "Contact of <strong>%{childContactName}</strong> will be deleted.",
|
||||||
"ATTRIBUTE_WARNING": "Contact details of <strong>%{primaryContactName}</strong> will be copied to <strong>%{parentContactName}</strong>."
|
"ATTRIBUTE_WARNING": "Contact details of <strong>%{childContactName}</strong> will be copied to <strong>%{primaryContactName}</strong>."
|
||||||
},
|
},
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"ERROR": "ERROR_MESSAGE"
|
"ERROR": "ERROR_MESSAGE"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
"ASSIGN_TO_ME": "Přiřadit mi",
|
"ASSIGN_TO_ME": "Přiřadit mi",
|
||||||
"TWILIO_WHATSAPP_CAN_REPLY": "Na tuto konverzaci můžete odpovědět pouze pomocí šablony zprávy z důvodu",
|
"TWILIO_WHATSAPP_CAN_REPLY": "Na tuto konverzaci můžete odpovědět pouze pomocí šablony zprávy z důvodu",
|
||||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 hodinové omezení okna",
|
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 hodinové omezení okna",
|
||||||
|
"SELECT_A_TWEET_TO_REPLY": "Vyberte prosím tweet, na který chcete odpovědět.",
|
||||||
"REPLYING_TO": "Odpovídáte uživateli:",
|
"REPLYING_TO": "Odpovídáte uživateli:",
|
||||||
"REMOVE_SELECTION": "Odstranit výběr",
|
"REMOVE_SELECTION": "Odstranit výběr",
|
||||||
"DOWNLOAD": "Stáhnout",
|
"DOWNLOAD": "Stáhnout",
|
||||||
@@ -46,7 +47,6 @@
|
|||||||
"NO_RESPONSE": "Bez odpovědi",
|
"NO_RESPONSE": "Bez odpovědi",
|
||||||
"RATING_TITLE": "Hodnocení",
|
"RATING_TITLE": "Hodnocení",
|
||||||
"FEEDBACK_TITLE": "Zpětná vazba",
|
"FEEDBACK_TITLE": "Zpětná vazba",
|
||||||
"REPLY_MESSAGE_NOT_FOUND": "Message not available",
|
|
||||||
"CARD": {
|
"CARD": {
|
||||||
"SHOW_LABELS": "Show labels",
|
"SHOW_LABELS": "Show labels",
|
||||||
"HIDE_LABELS": "Hide labels"
|
"HIDE_LABELS": "Hide labels"
|
||||||
@@ -139,8 +139,7 @@
|
|||||||
"PRIVATE_NOTE": "Soukromá poznámka",
|
"PRIVATE_NOTE": "Soukromá poznámka",
|
||||||
"SEND": "Poslat",
|
"SEND": "Poslat",
|
||||||
"CREATE": "Přidat poznámku",
|
"CREATE": "Přidat poznámku",
|
||||||
"DISMISS_REPLY": "Dismiss reply",
|
"TWEET": "Tweet",
|
||||||
"REPLYING_TO": "Replying to:",
|
|
||||||
"TIP_FORMAT_ICON": "Zobrazit formátovaný textový editor",
|
"TIP_FORMAT_ICON": "Zobrazit formátovaný textový editor",
|
||||||
"TIP_EMOJI_ICON": "Zobrazit výběr emoji",
|
"TIP_EMOJI_ICON": "Zobrazit výběr emoji",
|
||||||
"TIP_ATTACH_ICON": "Přiložit soubory",
|
"TIP_ATTACH_ICON": "Přiložit soubory",
|
||||||
@@ -195,7 +194,6 @@
|
|||||||
},
|
},
|
||||||
"CONTEXT_MENU": {
|
"CONTEXT_MENU": {
|
||||||
"COPY": "Kopírovat",
|
"COPY": "Kopírovat",
|
||||||
"REPLY_TO": "Reply to this message",
|
|
||||||
"DELETE": "Vymazat",
|
"DELETE": "Vymazat",
|
||||||
"CREATE_A_CANNED_RESPONSE": "Add to canned responses",
|
"CREATE_A_CANNED_RESPONSE": "Add to canned responses",
|
||||||
"TRANSLATE": "Translate",
|
"TRANSLATE": "Translate",
|
||||||
|
|||||||
@@ -425,33 +425,6 @@
|
|||||||
"ERROR_MESSAGE": "Unable to delete category"
|
"ERROR_MESSAGE": "Unable to delete category"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"UPGRADE_PAGE": {
|
|
||||||
"TITLE": "Help Center",
|
|
||||||
"DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Upgrade your subscription to enable this feature.",
|
|
||||||
"SELF_HOSTED_DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Please contact your administrator to enable this feature.",
|
|
||||||
"BUTTON": {
|
|
||||||
"LEARN_MORE": "Learn more",
|
|
||||||
"UPGRADE": "Upgrade"
|
|
||||||
},
|
|
||||||
"FEATURES": {
|
|
||||||
"PORTALS": {
|
|
||||||
"TITLE": "Multiple portals",
|
|
||||||
"DESCRIPTION": "Create multiple help center portals for different products using the same account."
|
|
||||||
},
|
|
||||||
"LOCALES": {
|
|
||||||
"TITLE": "Full support for locales",
|
|
||||||
"DESCRIPTION": "Localize the portal in your language. We support all locales and allow translations for every article."
|
|
||||||
},
|
|
||||||
"SEO": {
|
|
||||||
"TITLE": "SEO-friendly design",
|
|
||||||
"DESCRIPTION": "Customize your meta tags to improve your visibility on search engines with our SEO-friendly pages."
|
|
||||||
},
|
|
||||||
"API": {
|
|
||||||
"TITLE": "Full API support",
|
|
||||||
"DESCRIPTION": "Use the portal as a headless CMS with third party front-end frameworks using our APIs."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,7 @@
|
|||||||
"REPORTS_OVERVIEW": "Overview",
|
"REPORTS_OVERVIEW": "Overview",
|
||||||
"FACEBOOK_REAUTHORIZE": "Your Facebook connection has expired, please reconnect your Facebook page to continue services",
|
"FACEBOOK_REAUTHORIZE": "Your Facebook connection has expired, please reconnect your Facebook page to continue services",
|
||||||
"HELP_CENTER": {
|
"HELP_CENTER": {
|
||||||
"TITLE": "Help Center",
|
"TITLE": "Help Center (Beta)",
|
||||||
"ALL_ARTICLES": "All Articles",
|
"ALL_ARTICLES": "All Articles",
|
||||||
"MY_ARTICLES": "My Articles",
|
"MY_ARTICLES": "My Articles",
|
||||||
"DRAFT": "Draft",
|
"DRAFT": "Draft",
|
||||||
|
|||||||
@@ -41,16 +41,6 @@
|
|||||||
"STATUS": "Status",
|
"STATUS": "Status",
|
||||||
"ORDER_BY": "Order by"
|
"ORDER_BY": "Order by"
|
||||||
},
|
},
|
||||||
"CHAT_TIME_STAMP": {
|
|
||||||
"CREATED": {
|
|
||||||
"LATEST": "Created",
|
|
||||||
"OLDEST": "Oprettet den:"
|
|
||||||
},
|
|
||||||
"LAST_ACTIVITY": {
|
|
||||||
"NOT_ACTIVE": "Last activity:",
|
|
||||||
"ACTIVE": "Last activity"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"CHAT_SORT_FILTER_ITEMS": {
|
"CHAT_SORT_FILTER_ITEMS": {
|
||||||
"latest": {
|
"latest": {
|
||||||
"TEXT": "Last activity"
|
"TEXT": "Last activity"
|
||||||
|
|||||||
@@ -204,10 +204,6 @@
|
|||||||
"PLACEHOLDER": "Skriv din besked her",
|
"PLACEHOLDER": "Skriv din besked her",
|
||||||
"ERROR": "Beskeden må ikke være tom"
|
"ERROR": "Beskeden må ikke være tom"
|
||||||
},
|
},
|
||||||
"ATTACHMENTS": {
|
|
||||||
"SELECT": "Choose files",
|
|
||||||
"HELP_TEXT": "Drag and drop files here or choose files to attach"
|
|
||||||
},
|
|
||||||
"SUBMIT": "Send besked",
|
"SUBMIT": "Send besked",
|
||||||
"CANCEL": "Annuller",
|
"CANCEL": "Annuller",
|
||||||
"SUCCESS_MESSAGE": "Besked sendt!",
|
"SUCCESS_MESSAGE": "Besked sendt!",
|
||||||
@@ -347,17 +343,17 @@
|
|||||||
"DESCRIPTION": "Sammenflet kontakter for at kombinere to profiler til én, herunder alle attributter og samtaler. I tilfælde af konflikter vil den primære kontakts attributter have forrang.",
|
"DESCRIPTION": "Sammenflet kontakter for at kombinere to profiler til én, herunder alle attributter og samtaler. I tilfælde af konflikter vil den primære kontakts attributter have forrang.",
|
||||||
"PRIMARY": {
|
"PRIMARY": {
|
||||||
"TITLE": "Primær kontakt",
|
"TITLE": "Primær kontakt",
|
||||||
"HELP_LABEL": "Skal slettes"
|
"HELP_LABEL": "Skal opbevares"
|
||||||
},
|
},
|
||||||
"PARENT": {
|
"CHILD": {
|
||||||
"TITLE": "Kontakt for sammenfletning",
|
"TITLE": "Kontakt for sammenfletning",
|
||||||
"PLACEHOLDER": "Søg efter en kontakt",
|
"PLACEHOLDER": "Søg efter en kontakt",
|
||||||
"HELP_LABEL": "Skal opbevares"
|
"HELP_LABEL": "Skal slettes"
|
||||||
},
|
},
|
||||||
"SUMMARY": {
|
"SUMMARY": {
|
||||||
"TITLE": "Oversigt",
|
"TITLE": "Oversigt",
|
||||||
"DELETE_WARNING": "Kontakt af <strong>%{primaryContactName}</strong> vil blive slettet.",
|
"DELETE_WARNING": "Kontakt af <strong>%{childContactName}</strong> vil blive slettet.",
|
||||||
"ATTRIBUTE_WARNING": "Kontaktoplysninger på <strong>%{primaryContactName}</strong> vil blive kopieret til <strong>%{parentContactName}</strong>."
|
"ATTRIBUTE_WARNING": "Kontaktoplysninger på <strong>%{childContactName}</strong> vil blive kopieret til <strong>%{primaryContactName}</strong>."
|
||||||
},
|
},
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"ERROR": "FEJL_MEDDELELSE"
|
"ERROR": "FEJL_MEDDELELSE"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
"ASSIGN_TO_ME": "Tildel til mig",
|
"ASSIGN_TO_ME": "Tildel til mig",
|
||||||
"TWILIO_WHATSAPP_CAN_REPLY": "Du kan kun svare på denne samtale ved hjælp af en skabelon besked på grund af",
|
"TWILIO_WHATSAPP_CAN_REPLY": "Du kan kun svare på denne samtale ved hjælp af en skabelon besked på grund af",
|
||||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 timers beskedvindue begrænsning",
|
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 timers beskedvindue begrænsning",
|
||||||
|
"SELECT_A_TWEET_TO_REPLY": "Vælg venligst et tweet at svare på.",
|
||||||
"REPLYING_TO": "Du svarer til:",
|
"REPLYING_TO": "Du svarer til:",
|
||||||
"REMOVE_SELECTION": "Fjern Markering",
|
"REMOVE_SELECTION": "Fjern Markering",
|
||||||
"DOWNLOAD": "Download",
|
"DOWNLOAD": "Download",
|
||||||
@@ -46,7 +47,6 @@
|
|||||||
"NO_RESPONSE": "Intet svar",
|
"NO_RESPONSE": "Intet svar",
|
||||||
"RATING_TITLE": "Bedømmelse",
|
"RATING_TITLE": "Bedømmelse",
|
||||||
"FEEDBACK_TITLE": "Tilbagemelding",
|
"FEEDBACK_TITLE": "Tilbagemelding",
|
||||||
"REPLY_MESSAGE_NOT_FOUND": "Message not available",
|
|
||||||
"CARD": {
|
"CARD": {
|
||||||
"SHOW_LABELS": "Vis etiketter",
|
"SHOW_LABELS": "Vis etiketter",
|
||||||
"HIDE_LABELS": "Skjul etiketter"
|
"HIDE_LABELS": "Skjul etiketter"
|
||||||
@@ -139,8 +139,7 @@
|
|||||||
"PRIVATE_NOTE": "Privat Note",
|
"PRIVATE_NOTE": "Privat Note",
|
||||||
"SEND": "Send",
|
"SEND": "Send",
|
||||||
"CREATE": "Tilføj Note",
|
"CREATE": "Tilføj Note",
|
||||||
"DISMISS_REPLY": "Dismiss reply",
|
"TWEET": "Tweet",
|
||||||
"REPLYING_TO": "Replying to:",
|
|
||||||
"TIP_FORMAT_ICON": "Vis rig teksteditor",
|
"TIP_FORMAT_ICON": "Vis rig teksteditor",
|
||||||
"TIP_EMOJI_ICON": "Vis emoji-vælger",
|
"TIP_EMOJI_ICON": "Vis emoji-vælger",
|
||||||
"TIP_ATTACH_ICON": "Vedhæft filer",
|
"TIP_ATTACH_ICON": "Vedhæft filer",
|
||||||
@@ -195,7 +194,6 @@
|
|||||||
},
|
},
|
||||||
"CONTEXT_MENU": {
|
"CONTEXT_MENU": {
|
||||||
"COPY": "Kopiér",
|
"COPY": "Kopiér",
|
||||||
"REPLY_TO": "Reply to this message",
|
|
||||||
"DELETE": "Slet",
|
"DELETE": "Slet",
|
||||||
"CREATE_A_CANNED_RESPONSE": "Add to canned responses",
|
"CREATE_A_CANNED_RESPONSE": "Add to canned responses",
|
||||||
"TRANSLATE": "Translate",
|
"TRANSLATE": "Translate",
|
||||||
|
|||||||
@@ -425,33 +425,6 @@
|
|||||||
"ERROR_MESSAGE": "Kan ikke slette kategori"
|
"ERROR_MESSAGE": "Kan ikke slette kategori"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"UPGRADE_PAGE": {
|
|
||||||
"TITLE": "Help Center",
|
|
||||||
"DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Upgrade your subscription to enable this feature.",
|
|
||||||
"SELF_HOSTED_DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Please contact your administrator to enable this feature.",
|
|
||||||
"BUTTON": {
|
|
||||||
"LEARN_MORE": "Lær mere",
|
|
||||||
"UPGRADE": "Upgrade"
|
|
||||||
},
|
|
||||||
"FEATURES": {
|
|
||||||
"PORTALS": {
|
|
||||||
"TITLE": "Multiple portals",
|
|
||||||
"DESCRIPTION": "Create multiple help center portals for different products using the same account."
|
|
||||||
},
|
|
||||||
"LOCALES": {
|
|
||||||
"TITLE": "Full support for locales",
|
|
||||||
"DESCRIPTION": "Localize the portal in your language. We support all locales and allow translations for every article."
|
|
||||||
},
|
|
||||||
"SEO": {
|
|
||||||
"TITLE": "SEO-friendly design",
|
|
||||||
"DESCRIPTION": "Customize your meta tags to improve your visibility on search engines with our SEO-friendly pages."
|
|
||||||
},
|
|
||||||
"API": {
|
|
||||||
"TITLE": "Full API support",
|
|
||||||
"DESCRIPTION": "Use the portal as a headless CMS with third party front-end frameworks using our APIs."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,7 @@
|
|||||||
"REPORTS_OVERVIEW": "Oversigt",
|
"REPORTS_OVERVIEW": "Oversigt",
|
||||||
"FACEBOOK_REAUTHORIZE": "Din Facebook-forbindelse er udløbet, tilslut venligst din Facebook-side igen for at fortsætte tjenesterne",
|
"FACEBOOK_REAUTHORIZE": "Din Facebook-forbindelse er udløbet, tilslut venligst din Facebook-side igen for at fortsætte tjenesterne",
|
||||||
"HELP_CENTER": {
|
"HELP_CENTER": {
|
||||||
"TITLE": "Help Center",
|
"TITLE": "Hjælpecenter (Beta)",
|
||||||
"ALL_ARTICLES": "Alle Artikler",
|
"ALL_ARTICLES": "Alle Artikler",
|
||||||
"MY_ARTICLES": "Alle Artikler",
|
"MY_ARTICLES": "Alle Artikler",
|
||||||
"DRAFT": "Kladde",
|
"DRAFT": "Kladde",
|
||||||
|
|||||||
@@ -41,16 +41,6 @@
|
|||||||
"STATUS": "Status",
|
"STATUS": "Status",
|
||||||
"ORDER_BY": "Sortieren nach"
|
"ORDER_BY": "Sortieren nach"
|
||||||
},
|
},
|
||||||
"CHAT_TIME_STAMP": {
|
|
||||||
"CREATED": {
|
|
||||||
"LATEST": "Erstellt",
|
|
||||||
"OLDEST": "Erstellt am:"
|
|
||||||
},
|
|
||||||
"LAST_ACTIVITY": {
|
|
||||||
"NOT_ACTIVE": "Letzte Aktivität:",
|
|
||||||
"ACTIVE": "Letzte Aktivität"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"CHAT_SORT_FILTER_ITEMS": {
|
"CHAT_SORT_FILTER_ITEMS": {
|
||||||
"latest": {
|
"latest": {
|
||||||
"TEXT": "Letzte Aktivität"
|
"TEXT": "Letzte Aktivität"
|
||||||
|
|||||||
@@ -204,10 +204,6 @@
|
|||||||
"PLACEHOLDER": "Schreiben Sie Ihre Nachricht hier",
|
"PLACEHOLDER": "Schreiben Sie Ihre Nachricht hier",
|
||||||
"ERROR": "Nachricht darf nicht leer sein"
|
"ERROR": "Nachricht darf nicht leer sein"
|
||||||
},
|
},
|
||||||
"ATTACHMENTS": {
|
|
||||||
"SELECT": "Choose files",
|
|
||||||
"HELP_TEXT": "Drag and drop files here or choose files to attach"
|
|
||||||
},
|
|
||||||
"SUBMIT": "Nachricht senden",
|
"SUBMIT": "Nachricht senden",
|
||||||
"CANCEL": "Abbrechen",
|
"CANCEL": "Abbrechen",
|
||||||
"SUCCESS_MESSAGE": "Nachricht gesendet!",
|
"SUCCESS_MESSAGE": "Nachricht gesendet!",
|
||||||
@@ -347,17 +343,17 @@
|
|||||||
"DESCRIPTION": "Kontakte zusammenführen, um zwei Profile zu einem zu kombinieren, einschließlich aller Attribute und Gespräche. Im Falle eines Konflikts haben die Attribute des Hauptkontakts Vorrang.",
|
"DESCRIPTION": "Kontakte zusammenführen, um zwei Profile zu einem zu kombinieren, einschließlich aller Attribute und Gespräche. Im Falle eines Konflikts haben die Attribute des Hauptkontakts Vorrang.",
|
||||||
"PRIMARY": {
|
"PRIMARY": {
|
||||||
"TITLE": "Hauptkontakt",
|
"TITLE": "Hauptkontakt",
|
||||||
"HELP_LABEL": "Zu löschen"
|
"HELP_LABEL": "Zu erhalten"
|
||||||
},
|
},
|
||||||
"PARENT": {
|
"CHILD": {
|
||||||
"TITLE": "Kontakt zum Zusammenführen",
|
"TITLE": "Kontakt zum Zusammenführen",
|
||||||
"PLACEHOLDER": "Nach Kontakt suchen",
|
"PLACEHOLDER": "Nach Kontakt suchen",
|
||||||
"HELP_LABEL": "Zu erhalten"
|
"HELP_LABEL": "Zu löschen"
|
||||||
},
|
},
|
||||||
"SUMMARY": {
|
"SUMMARY": {
|
||||||
"TITLE": "Zusammenfassung",
|
"TITLE": "Zusammenfassung",
|
||||||
"DELETE_WARNING": "Der Kontakt von <strong>%{primaryContactName}</strong> wird gelöscht.",
|
"DELETE_WARNING": "Der Kontakt von <strong>%{childContactName}</strong> wird gelöscht.",
|
||||||
"ATTRIBUTE_WARNING": "Details von Kontakt <strong>%{primaryContactName}</strong> wird zu <strong>%{parentContactName}</strong> kopiert."
|
"ATTRIBUTE_WARNING": "Details von Kontakt <strong>%{childContactName}</strong> wird zu <strong>%{primaryContactName}</strong> kopiert."
|
||||||
},
|
},
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"ERROR": "ERROR_MESSAGE"
|
"ERROR": "ERROR_MESSAGE"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
"ASSIGN_TO_ME": "Mir zuweisen",
|
"ASSIGN_TO_ME": "Mir zuweisen",
|
||||||
"TWILIO_WHATSAPP_CAN_REPLY": "Sie können auf diese Konversation nur mit einer Nachrichtenvorlage antworten wegen",
|
"TWILIO_WHATSAPP_CAN_REPLY": "Sie können auf diese Konversation nur mit einer Nachrichtenvorlage antworten wegen",
|
||||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24-Stunden-Nachrichtenfenster-Beschränkung",
|
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24-Stunden-Nachrichtenfenster-Beschränkung",
|
||||||
|
"SELECT_A_TWEET_TO_REPLY": "Bitte wählen Sie einen Tweet aus, auf welchen Sie antworten möchten.",
|
||||||
"REPLYING_TO": "Sie antworten auf:",
|
"REPLYING_TO": "Sie antworten auf:",
|
||||||
"REMOVE_SELECTION": "Auswahl entfernen",
|
"REMOVE_SELECTION": "Auswahl entfernen",
|
||||||
"DOWNLOAD": "Herunterladen",
|
"DOWNLOAD": "Herunterladen",
|
||||||
@@ -46,7 +47,6 @@
|
|||||||
"NO_RESPONSE": "Keine Antwort",
|
"NO_RESPONSE": "Keine Antwort",
|
||||||
"RATING_TITLE": "Bewertung",
|
"RATING_TITLE": "Bewertung",
|
||||||
"FEEDBACK_TITLE": "Feedback",
|
"FEEDBACK_TITLE": "Feedback",
|
||||||
"REPLY_MESSAGE_NOT_FOUND": "Message not available",
|
|
||||||
"CARD": {
|
"CARD": {
|
||||||
"SHOW_LABELS": "Labels anzeigen",
|
"SHOW_LABELS": "Labels anzeigen",
|
||||||
"HIDE_LABELS": "Labels ausblenden"
|
"HIDE_LABELS": "Labels ausblenden"
|
||||||
@@ -139,8 +139,7 @@
|
|||||||
"PRIVATE_NOTE": "Private Notiz",
|
"PRIVATE_NOTE": "Private Notiz",
|
||||||
"SEND": "Senden",
|
"SEND": "Senden",
|
||||||
"CREATE": "Notiz hinzufügen",
|
"CREATE": "Notiz hinzufügen",
|
||||||
"DISMISS_REPLY": "Dismiss reply",
|
"TWEET": "Tweet",
|
||||||
"REPLYING_TO": "Replying to:",
|
|
||||||
"TIP_FORMAT_ICON": "Rich Text Editor anzeigen",
|
"TIP_FORMAT_ICON": "Rich Text Editor anzeigen",
|
||||||
"TIP_EMOJI_ICON": "Emoji-Auswahl anzeigen",
|
"TIP_EMOJI_ICON": "Emoji-Auswahl anzeigen",
|
||||||
"TIP_ATTACH_ICON": "Dateien anhängen",
|
"TIP_ATTACH_ICON": "Dateien anhängen",
|
||||||
@@ -195,7 +194,6 @@
|
|||||||
},
|
},
|
||||||
"CONTEXT_MENU": {
|
"CONTEXT_MENU": {
|
||||||
"COPY": "Kopieren",
|
"COPY": "Kopieren",
|
||||||
"REPLY_TO": "Reply to this message",
|
|
||||||
"DELETE": "Löschen",
|
"DELETE": "Löschen",
|
||||||
"CREATE_A_CANNED_RESPONSE": "Zu vorgefertigten Antworten hinzufügen",
|
"CREATE_A_CANNED_RESPONSE": "Zu vorgefertigten Antworten hinzufügen",
|
||||||
"TRANSLATE": "Übersetzen",
|
"TRANSLATE": "Übersetzen",
|
||||||
|
|||||||
@@ -425,33 +425,6 @@
|
|||||||
"ERROR_MESSAGE": "Kategorie kann nicht gelöscht werden"
|
"ERROR_MESSAGE": "Kategorie kann nicht gelöscht werden"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"UPGRADE_PAGE": {
|
|
||||||
"TITLE": "Hilfezentrum",
|
|
||||||
"DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Upgrade your subscription to enable this feature.",
|
|
||||||
"SELF_HOSTED_DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Please contact your administrator to enable this feature.",
|
|
||||||
"BUTTON": {
|
|
||||||
"LEARN_MORE": "Mehr erfahren",
|
|
||||||
"UPGRADE": "Upgrade"
|
|
||||||
},
|
|
||||||
"FEATURES": {
|
|
||||||
"PORTALS": {
|
|
||||||
"TITLE": "Multiple portals",
|
|
||||||
"DESCRIPTION": "Create multiple help center portals for different products using the same account."
|
|
||||||
},
|
|
||||||
"LOCALES": {
|
|
||||||
"TITLE": "Full support for locales",
|
|
||||||
"DESCRIPTION": "Localize the portal in your language. We support all locales and allow translations for every article."
|
|
||||||
},
|
|
||||||
"SEO": {
|
|
||||||
"TITLE": "SEO-friendly design",
|
|
||||||
"DESCRIPTION": "Customize your meta tags to improve your visibility on search engines with our SEO-friendly pages."
|
|
||||||
},
|
|
||||||
"API": {
|
|
||||||
"TITLE": "Full API support",
|
|
||||||
"DESCRIPTION": "Use the portal as a headless CMS with third party front-end frameworks using our APIs."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,7 @@
|
|||||||
"REPORTS_OVERVIEW": "Übersicht",
|
"REPORTS_OVERVIEW": "Übersicht",
|
||||||
"FACEBOOK_REAUTHORIZE": "Ihre Facebook-Verbindung ist abgelaufen, bitte verbinden Sie sich erneut.",
|
"FACEBOOK_REAUTHORIZE": "Ihre Facebook-Verbindung ist abgelaufen, bitte verbinden Sie sich erneut.",
|
||||||
"HELP_CENTER": {
|
"HELP_CENTER": {
|
||||||
"TITLE": "Hilfezentrum",
|
"TITLE": "Hilfezentrum (Beta)",
|
||||||
"ALL_ARTICLES": "Alle Artikel",
|
"ALL_ARTICLES": "Alle Artikel",
|
||||||
"MY_ARTICLES": "Meine Artikel",
|
"MY_ARTICLES": "Meine Artikel",
|
||||||
"DRAFT": "Entwürfe",
|
"DRAFT": "Entwürfe",
|
||||||
|
|||||||
@@ -41,16 +41,6 @@
|
|||||||
"STATUS": "Κατάσταση",
|
"STATUS": "Κατάσταση",
|
||||||
"ORDER_BY": "Order by"
|
"ORDER_BY": "Order by"
|
||||||
},
|
},
|
||||||
"CHAT_TIME_STAMP": {
|
|
||||||
"CREATED": {
|
|
||||||
"LATEST": "Created",
|
|
||||||
"OLDEST": "Δημιουργήθηκε στις:"
|
|
||||||
},
|
|
||||||
"LAST_ACTIVITY": {
|
|
||||||
"NOT_ACTIVE": "Last activity:",
|
|
||||||
"ACTIVE": "Last activity"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"CHAT_SORT_FILTER_ITEMS": {
|
"CHAT_SORT_FILTER_ITEMS": {
|
||||||
"latest": {
|
"latest": {
|
||||||
"TEXT": "Last activity"
|
"TEXT": "Last activity"
|
||||||
|
|||||||
@@ -204,10 +204,6 @@
|
|||||||
"PLACEHOLDER": "Γράψτε το μήνυμά σας εδώ",
|
"PLACEHOLDER": "Γράψτε το μήνυμά σας εδώ",
|
||||||
"ERROR": "Το μήνυμα δεν μπορεί να είναι άδειο"
|
"ERROR": "Το μήνυμα δεν μπορεί να είναι άδειο"
|
||||||
},
|
},
|
||||||
"ATTACHMENTS": {
|
|
||||||
"SELECT": "Choose files",
|
|
||||||
"HELP_TEXT": "Drag and drop files here or choose files to attach"
|
|
||||||
},
|
|
||||||
"SUBMIT": "Αποστολή μηνύματος",
|
"SUBMIT": "Αποστολή μηνύματος",
|
||||||
"CANCEL": "Άκυρο",
|
"CANCEL": "Άκυρο",
|
||||||
"SUCCESS_MESSAGE": "Το μήνυμα στάλθηκε!",
|
"SUCCESS_MESSAGE": "Το μήνυμα στάλθηκε!",
|
||||||
@@ -347,17 +343,17 @@
|
|||||||
"DESCRIPTION": "Συγχώνευση επαφών, για να συνδυάσετε δύο προφίλ σε ένα, συμπεριλαμβανομένων όλων των χαρακτηριστικών και συνομιλιών. Σε περίπτωση σύγκρουσης, τα χαρακτηριστικά της κύριας επαφής υπερισχύουν.",
|
"DESCRIPTION": "Συγχώνευση επαφών, για να συνδυάσετε δύο προφίλ σε ένα, συμπεριλαμβανομένων όλων των χαρακτηριστικών και συνομιλιών. Σε περίπτωση σύγκρουσης, τα χαρακτηριστικά της κύριας επαφής υπερισχύουν.",
|
||||||
"PRIMARY": {
|
"PRIMARY": {
|
||||||
"TITLE": "Κύρια επαφή",
|
"TITLE": "Κύρια επαφή",
|
||||||
"HELP_LABEL": "Να διαγραφεί"
|
"HELP_LABEL": "Να διατηρούνται"
|
||||||
},
|
},
|
||||||
"PARENT": {
|
"CHILD": {
|
||||||
"TITLE": "Επαφή για συγχώνευση",
|
"TITLE": "Επαφή για συγχώνευση",
|
||||||
"PLACEHOLDER": "Αναζήτηση επαφής",
|
"PLACEHOLDER": "Αναζήτηση επαφής",
|
||||||
"HELP_LABEL": "Να διατηρούνται"
|
"HELP_LABEL": "Να διαγραφεί"
|
||||||
},
|
},
|
||||||
"SUMMARY": {
|
"SUMMARY": {
|
||||||
"TITLE": "Σύνοψη",
|
"TITLE": "Σύνοψη",
|
||||||
"DELETE_WARNING": "Η επαφή του <strong>%{primaryContactName}</strong>θα διαγραφεί.",
|
"DELETE_WARNING": "Η επαφή του <strong>%{childContactName}</strong>θα διαγραφεί.",
|
||||||
"ATTRIBUTE_WARNING": "Τα στοιχεία επικοινωνίας του <strong>%{primaryContactName}</strong> θα αντιγραφούν στο <strong>%{parentContactName}</strong>."
|
"ATTRIBUTE_WARNING": "Τα στοιχεία επικοινωνίας του <strong>%{childContactName}</strong> θα αντιγραφούν στο <strong>%{primaryContactName}</strong>."
|
||||||
},
|
},
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"ERROR": "ERROR_MESSAGE"
|
"ERROR": "ERROR_MESSAGE"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
"ASSIGN_TO_ME": "Ανάθεση σε μένα",
|
"ASSIGN_TO_ME": "Ανάθεση σε μένα",
|
||||||
"TWILIO_WHATSAPP_CAN_REPLY": "Μπορείτε να απαντήσετε μόνο σε αυτή τη συνομιλία χρησιμοποιώντας ένα πρότυπο μήνυμα επειδή",
|
"TWILIO_WHATSAPP_CAN_REPLY": "Μπορείτε να απαντήσετε μόνο σε αυτή τη συνομιλία χρησιμοποιώντας ένα πρότυπο μήνυμα επειδή",
|
||||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "του περιορισμού των 24 ωρών",
|
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "του περιορισμού των 24 ωρών",
|
||||||
|
"SELECT_A_TWEET_TO_REPLY": "Παρακαλώ επιλέξτε ένα tweet για να απαντήσετε.",
|
||||||
"REPLYING_TO": "Απαντάτε στο:",
|
"REPLYING_TO": "Απαντάτε στο:",
|
||||||
"REMOVE_SELECTION": "Διαγραφή Επιλογής",
|
"REMOVE_SELECTION": "Διαγραφή Επιλογής",
|
||||||
"DOWNLOAD": "Κατέβασμα",
|
"DOWNLOAD": "Κατέβασμα",
|
||||||
@@ -46,7 +47,6 @@
|
|||||||
"NO_RESPONSE": "Καμία ανταπόκριση",
|
"NO_RESPONSE": "Καμία ανταπόκριση",
|
||||||
"RATING_TITLE": "Αξιολόγηση",
|
"RATING_TITLE": "Αξιολόγηση",
|
||||||
"FEEDBACK_TITLE": "Ανατροφοδότηση",
|
"FEEDBACK_TITLE": "Ανατροφοδότηση",
|
||||||
"REPLY_MESSAGE_NOT_FOUND": "Message not available",
|
|
||||||
"CARD": {
|
"CARD": {
|
||||||
"SHOW_LABELS": "Προβολή ετικετών",
|
"SHOW_LABELS": "Προβολή ετικετών",
|
||||||
"HIDE_LABELS": "Απόκρυψη ετικετών"
|
"HIDE_LABELS": "Απόκρυψη ετικετών"
|
||||||
@@ -139,8 +139,7 @@
|
|||||||
"PRIVATE_NOTE": "Ιδιωτική Σημείωση",
|
"PRIVATE_NOTE": "Ιδιωτική Σημείωση",
|
||||||
"SEND": "Αποστολή",
|
"SEND": "Αποστολή",
|
||||||
"CREATE": "Σημείωση (Add Note)",
|
"CREATE": "Σημείωση (Add Note)",
|
||||||
"DISMISS_REPLY": "Dismiss reply",
|
"TWEET": "Tweet",
|
||||||
"REPLYING_TO": "Replying to:",
|
|
||||||
"TIP_FORMAT_ICON": "Προβολή επεξεργαστή εμπλουτισμένου κειμένου",
|
"TIP_FORMAT_ICON": "Προβολή επεξεργαστή εμπλουτισμένου κειμένου",
|
||||||
"TIP_EMOJI_ICON": "Προβολή επιλογέα emoji",
|
"TIP_EMOJI_ICON": "Προβολή επιλογέα emoji",
|
||||||
"TIP_ATTACH_ICON": "Επισύναψη αρχείων",
|
"TIP_ATTACH_ICON": "Επισύναψη αρχείων",
|
||||||
@@ -195,7 +194,6 @@
|
|||||||
},
|
},
|
||||||
"CONTEXT_MENU": {
|
"CONTEXT_MENU": {
|
||||||
"COPY": "Αντιγραφή",
|
"COPY": "Αντιγραφή",
|
||||||
"REPLY_TO": "Reply to this message",
|
|
||||||
"DELETE": "Διαγραφή",
|
"DELETE": "Διαγραφή",
|
||||||
"CREATE_A_CANNED_RESPONSE": "Προσθήκη στις έτοιμες απαντήσεις",
|
"CREATE_A_CANNED_RESPONSE": "Προσθήκη στις έτοιμες απαντήσεις",
|
||||||
"TRANSLATE": "Translate",
|
"TRANSLATE": "Translate",
|
||||||
|
|||||||
@@ -425,33 +425,6 @@
|
|||||||
"ERROR_MESSAGE": "Δεν είναι δυνατή η διαγραφή κατηγορίας"
|
"ERROR_MESSAGE": "Δεν είναι δυνατή η διαγραφή κατηγορίας"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"UPGRADE_PAGE": {
|
|
||||||
"TITLE": "Help Center",
|
|
||||||
"DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Upgrade your subscription to enable this feature.",
|
|
||||||
"SELF_HOSTED_DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Please contact your administrator to enable this feature.",
|
|
||||||
"BUTTON": {
|
|
||||||
"LEARN_MORE": "Μάθετε περισσότερα",
|
|
||||||
"UPGRADE": "Upgrade"
|
|
||||||
},
|
|
||||||
"FEATURES": {
|
|
||||||
"PORTALS": {
|
|
||||||
"TITLE": "Multiple portals",
|
|
||||||
"DESCRIPTION": "Create multiple help center portals for different products using the same account."
|
|
||||||
},
|
|
||||||
"LOCALES": {
|
|
||||||
"TITLE": "Full support for locales",
|
|
||||||
"DESCRIPTION": "Localize the portal in your language. We support all locales and allow translations for every article."
|
|
||||||
},
|
|
||||||
"SEO": {
|
|
||||||
"TITLE": "SEO-friendly design",
|
|
||||||
"DESCRIPTION": "Customize your meta tags to improve your visibility on search engines with our SEO-friendly pages."
|
|
||||||
},
|
|
||||||
"API": {
|
|
||||||
"TITLE": "Full API support",
|
|
||||||
"DESCRIPTION": "Use the portal as a headless CMS with third party front-end frameworks using our APIs."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,7 @@
|
|||||||
"REPORTS_OVERVIEW": "Επισκόπηση",
|
"REPORTS_OVERVIEW": "Επισκόπηση",
|
||||||
"FACEBOOK_REAUTHORIZE": "Η σύνδεση Facebook έχει λήξει, παρακαλώ ξανασυνδεθείτε στο Facebook για να συνεχίσετε",
|
"FACEBOOK_REAUTHORIZE": "Η σύνδεση Facebook έχει λήξει, παρακαλώ ξανασυνδεθείτε στο Facebook για να συνεχίσετε",
|
||||||
"HELP_CENTER": {
|
"HELP_CENTER": {
|
||||||
"TITLE": "Help Center",
|
"TITLE": "Κέντρο Βοήθειας (Beta)",
|
||||||
"ALL_ARTICLES": "Όλα Τα Άρθρα",
|
"ALL_ARTICLES": "Όλα Τα Άρθρα",
|
||||||
"MY_ARTICLES": "Τα Άρθρα Μου",
|
"MY_ARTICLES": "Τα Άρθρα Μου",
|
||||||
"DRAFT": "Πρόχειρο",
|
"DRAFT": "Πρόχειρο",
|
||||||
|
|||||||
@@ -204,10 +204,6 @@
|
|||||||
"PLACEHOLDER": "Write your message here",
|
"PLACEHOLDER": "Write your message here",
|
||||||
"ERROR": "Message can't be empty"
|
"ERROR": "Message can't be empty"
|
||||||
},
|
},
|
||||||
"ATTACHMENTS": {
|
|
||||||
"SELECT": "Choose files",
|
|
||||||
"HELP_TEXT": "Drag and drop files here or choose files to attach"
|
|
||||||
},
|
|
||||||
"SUBMIT": "Send message",
|
"SUBMIT": "Send message",
|
||||||
"CANCEL": "Cancel",
|
"CANCEL": "Cancel",
|
||||||
"SUCCESS_MESSAGE": "Message sent!",
|
"SUCCESS_MESSAGE": "Message sent!",
|
||||||
@@ -347,17 +343,17 @@
|
|||||||
"DESCRIPTION": "Merge contacts to combine two profiles into one, including all attributes and conversations. In case of conflict, the Primary contact’s attributes will take precedence.",
|
"DESCRIPTION": "Merge contacts to combine two profiles into one, including all attributes and conversations. In case of conflict, the Primary contact’s attributes will take precedence.",
|
||||||
"PRIMARY": {
|
"PRIMARY": {
|
||||||
"TITLE": "Primary contact",
|
"TITLE": "Primary contact",
|
||||||
"HELP_LABEL": "To be deleted"
|
"HELP_LABEL": "To be kept"
|
||||||
},
|
},
|
||||||
"PARENT": {
|
"CHILD": {
|
||||||
"TITLE": "Contact to merge",
|
"TITLE": "Contact to merge",
|
||||||
"PLACEHOLDER": "Search for a contact",
|
"PLACEHOLDER": "Search for a contact",
|
||||||
"HELP_LABEL": "To be kept"
|
"HELP_LABEL": "To be deleted"
|
||||||
},
|
},
|
||||||
"SUMMARY": {
|
"SUMMARY": {
|
||||||
"TITLE": "Summary",
|
"TITLE": "Summary",
|
||||||
"DELETE_WARNING": "Contact of <strong>%{primaryContactName}</strong> will be deleted.",
|
"DELETE_WARNING": "Contact of <strong>%{childContactName}</strong> will be deleted.",
|
||||||
"ATTRIBUTE_WARNING": "Contact details of <strong>%{primaryContactName}</strong> will be copied to <strong>%{parentContactName}</strong>."
|
"ATTRIBUTE_WARNING": "Contact details of <strong>%{childContactName}</strong> will be copied to <strong>%{primaryContactName}</strong>."
|
||||||
},
|
},
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"ERROR": "ERROR_MESSAGE"
|
"ERROR": "ERROR_MESSAGE"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
"ASSIGN_TO_ME": "Assign to me",
|
"ASSIGN_TO_ME": "Assign to me",
|
||||||
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
|
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
|
||||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 hour message window restriction",
|
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24 hour message window restriction",
|
||||||
|
"SELECT_A_TWEET_TO_REPLY": "Please select a tweet to reply to.",
|
||||||
"REPLYING_TO": "You are replying to:",
|
"REPLYING_TO": "You are replying to:",
|
||||||
"REMOVE_SELECTION": "Remove Selection",
|
"REMOVE_SELECTION": "Remove Selection",
|
||||||
"DOWNLOAD": "Download",
|
"DOWNLOAD": "Download",
|
||||||
@@ -46,7 +47,6 @@
|
|||||||
"NO_RESPONSE": "No response",
|
"NO_RESPONSE": "No response",
|
||||||
"RATING_TITLE": "Rating",
|
"RATING_TITLE": "Rating",
|
||||||
"FEEDBACK_TITLE": "Feedback",
|
"FEEDBACK_TITLE": "Feedback",
|
||||||
"REPLY_MESSAGE_NOT_FOUND": "Message not available",
|
|
||||||
"CARD": {
|
"CARD": {
|
||||||
"SHOW_LABELS": "Show labels",
|
"SHOW_LABELS": "Show labels",
|
||||||
"HIDE_LABELS": "Hide labels"
|
"HIDE_LABELS": "Hide labels"
|
||||||
@@ -139,8 +139,7 @@
|
|||||||
"PRIVATE_NOTE": "Private Note",
|
"PRIVATE_NOTE": "Private Note",
|
||||||
"SEND": "Send",
|
"SEND": "Send",
|
||||||
"CREATE": "Add Note",
|
"CREATE": "Add Note",
|
||||||
"DISMISS_REPLY": "Dismiss reply",
|
"TWEET": "Tweet",
|
||||||
"REPLYING_TO": "Replying to:",
|
|
||||||
"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",
|
||||||
@@ -195,7 +194,6 @@
|
|||||||
},
|
},
|
||||||
"CONTEXT_MENU": {
|
"CONTEXT_MENU": {
|
||||||
"COPY": "Copy",
|
"COPY": "Copy",
|
||||||
"REPLY_TO": "Reply to this message",
|
|
||||||
"DELETE": "Delete",
|
"DELETE": "Delete",
|
||||||
"CREATE_A_CANNED_RESPONSE": "Add to canned responses",
|
"CREATE_A_CANNED_RESPONSE": "Add to canned responses",
|
||||||
"TRANSLATE": "Translate",
|
"TRANSLATE": "Translate",
|
||||||
|
|||||||
@@ -425,33 +425,6 @@
|
|||||||
"ERROR_MESSAGE": "Unable to delete category"
|
"ERROR_MESSAGE": "Unable to delete category"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"UPGRADE_PAGE": {
|
|
||||||
"TITLE": "Help Center",
|
|
||||||
"DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Upgrade your subscription to enable this feature.",
|
|
||||||
"SELF_HOSTED_DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Please contact your administrator to enable this feature.",
|
|
||||||
"BUTTON": {
|
|
||||||
"LEARN_MORE": "Learn more",
|
|
||||||
"UPGRADE": "Upgrade"
|
|
||||||
},
|
|
||||||
"FEATURES": {
|
|
||||||
"PORTALS": {
|
|
||||||
"TITLE": "Multiple portals",
|
|
||||||
"DESCRIPTION": "Create multiple help center portals for different products using the same account."
|
|
||||||
},
|
|
||||||
"LOCALES": {
|
|
||||||
"TITLE": "Full support for locales",
|
|
||||||
"DESCRIPTION": "Localize the portal in your language. We support all locales and allow translations for every article."
|
|
||||||
},
|
|
||||||
"SEO": {
|
|
||||||
"TITLE": "SEO-friendly design",
|
|
||||||
"DESCRIPTION": "Customize your meta tags to improve your visibility on search engines with our SEO-friendly pages."
|
|
||||||
},
|
|
||||||
"API": {
|
|
||||||
"TITLE": "Full API support",
|
|
||||||
"DESCRIPTION": "Use the portal as a headless CMS with third party front-end frameworks using our APIs."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,7 +238,7 @@
|
|||||||
"REPORTS_OVERVIEW": "Overview",
|
"REPORTS_OVERVIEW": "Overview",
|
||||||
"FACEBOOK_REAUTHORIZE": "Your Facebook connection has expired, please reconnect your Facebook page to continue services",
|
"FACEBOOK_REAUTHORIZE": "Your Facebook connection has expired, please reconnect your Facebook page to continue services",
|
||||||
"HELP_CENTER": {
|
"HELP_CENTER": {
|
||||||
"TITLE": "Help Center",
|
"TITLE": "Help Center (Beta)",
|
||||||
"ALL_ARTICLES": "All Articles",
|
"ALL_ARTICLES": "All Articles",
|
||||||
"MY_ARTICLES": "My Articles",
|
"MY_ARTICLES": "My Articles",
|
||||||
"DRAFT": "Draft",
|
"DRAFT": "Draft",
|
||||||
|
|||||||
@@ -41,16 +41,6 @@
|
|||||||
"STATUS": "Estado",
|
"STATUS": "Estado",
|
||||||
"ORDER_BY": "Ordenar por"
|
"ORDER_BY": "Ordenar por"
|
||||||
},
|
},
|
||||||
"CHAT_TIME_STAMP": {
|
|
||||||
"CREATED": {
|
|
||||||
"LATEST": "Creado",
|
|
||||||
"OLDEST": "Creado el:"
|
|
||||||
},
|
|
||||||
"LAST_ACTIVITY": {
|
|
||||||
"NOT_ACTIVE": "Última actividad:",
|
|
||||||
"ACTIVE": "Última actividad"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"CHAT_SORT_FILTER_ITEMS": {
|
"CHAT_SORT_FILTER_ITEMS": {
|
||||||
"latest": {
|
"latest": {
|
||||||
"TEXT": "Última actividad"
|
"TEXT": "Última actividad"
|
||||||
|
|||||||
@@ -204,10 +204,6 @@
|
|||||||
"PLACEHOLDER": "Escriba su mensaje aquí",
|
"PLACEHOLDER": "Escriba su mensaje aquí",
|
||||||
"ERROR": "El mensaje no puede estar vacío"
|
"ERROR": "El mensaje no puede estar vacío"
|
||||||
},
|
},
|
||||||
"ATTACHMENTS": {
|
|
||||||
"SELECT": "Seleccionar archivos",
|
|
||||||
"HELP_TEXT": "Arrastre y suelte los archivos aquí o elige archivos para adjuntar"
|
|
||||||
},
|
|
||||||
"SUBMIT": "Enviar mensaje",
|
"SUBMIT": "Enviar mensaje",
|
||||||
"CANCEL": "Cancelar",
|
"CANCEL": "Cancelar",
|
||||||
"SUCCESS_MESSAGE": "¡Mensaje enviado!",
|
"SUCCESS_MESSAGE": "¡Mensaje enviado!",
|
||||||
@@ -347,17 +343,17 @@
|
|||||||
"DESCRIPTION": "Fusionar los contactos para combinar dos perfiles en uno, incluyendo todos los atributos y conversaciones.",
|
"DESCRIPTION": "Fusionar los contactos para combinar dos perfiles en uno, incluyendo todos los atributos y conversaciones.",
|
||||||
"PRIMARY": {
|
"PRIMARY": {
|
||||||
"TITLE": "Contacto principal",
|
"TITLE": "Contacto principal",
|
||||||
"HELP_LABEL": "A eliminar"
|
"HELP_LABEL": "Mantener"
|
||||||
},
|
},
|
||||||
"PARENT": {
|
"CHILD": {
|
||||||
"TITLE": "Contacto para combinar",
|
"TITLE": "Contacto para combinar",
|
||||||
"PLACEHOLDER": "Buscar un contacto",
|
"PLACEHOLDER": "Buscar un contacto",
|
||||||
"HELP_LABEL": "Mantener"
|
"HELP_LABEL": "A eliminar"
|
||||||
},
|
},
|
||||||
"SUMMARY": {
|
"SUMMARY": {
|
||||||
"TITLE": "Resumen",
|
"TITLE": "Resumen",
|
||||||
"DELETE_WARNING": "El contacto de <strong>%{primaryContactName}</strong> se eliminará.",
|
"DELETE_WARNING": "El contacto de <strong>%{childContactName}</strong> se eliminará.",
|
||||||
"ATTRIBUTE_WARNING": "Los datos de contacto de <strong>%{primaryContactName}</strong> se copiarán en <strong>%{parentContactName}</strong>."
|
"ATTRIBUTE_WARNING": "Los datos de contacto de <strong>%{childContactName}</strong> se copiarán en <strong>%{primaryContactName}</strong>."
|
||||||
},
|
},
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"ERROR": "ERROR"
|
"ERROR": "ERROR"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
"ASSIGN_TO_ME": "Asignar a mi",
|
"ASSIGN_TO_ME": "Asignar a mi",
|
||||||
"TWILIO_WHATSAPP_CAN_REPLY": "Sólo puede responder a esta conversación usando una plantilla de mensaje debido a",
|
"TWILIO_WHATSAPP_CAN_REPLY": "Sólo puede responder a esta conversación usando una plantilla de mensaje debido a",
|
||||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "Restricción de la ventana de mensajes de 24 horas",
|
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "Restricción de la ventana de mensajes de 24 horas",
|
||||||
|
"SELECT_A_TWEET_TO_REPLY": "Por favor, seleccione un tweet al que responder.",
|
||||||
"REPLYING_TO": "Esta respondiendo a:",
|
"REPLYING_TO": "Esta respondiendo a:",
|
||||||
"REMOVE_SELECTION": "Eliminar selección",
|
"REMOVE_SELECTION": "Eliminar selección",
|
||||||
"DOWNLOAD": "Descargar",
|
"DOWNLOAD": "Descargar",
|
||||||
@@ -46,7 +47,6 @@
|
|||||||
"NO_RESPONSE": "No hay respuesta",
|
"NO_RESPONSE": "No hay respuesta",
|
||||||
"RATING_TITLE": "Calificación",
|
"RATING_TITLE": "Calificación",
|
||||||
"FEEDBACK_TITLE": "Comentarios",
|
"FEEDBACK_TITLE": "Comentarios",
|
||||||
"REPLY_MESSAGE_NOT_FOUND": "Mensaje no disponible",
|
|
||||||
"CARD": {
|
"CARD": {
|
||||||
"SHOW_LABELS": "Mostrar etiquetas",
|
"SHOW_LABELS": "Mostrar etiquetas",
|
||||||
"HIDE_LABELS": "Ocultar etiquetas"
|
"HIDE_LABELS": "Ocultar etiquetas"
|
||||||
@@ -139,8 +139,7 @@
|
|||||||
"PRIVATE_NOTE": "Nota privada",
|
"PRIVATE_NOTE": "Nota privada",
|
||||||
"SEND": "Enviar",
|
"SEND": "Enviar",
|
||||||
"CREATE": "Añadir nota",
|
"CREATE": "Añadir nota",
|
||||||
"DISMISS_REPLY": "Dismiss reply",
|
"TWEET": "Tweet",
|
||||||
"REPLYING_TO": "Replying to:",
|
|
||||||
"TIP_FORMAT_ICON": "Mostrar editor de textos",
|
"TIP_FORMAT_ICON": "Mostrar editor de textos",
|
||||||
"TIP_EMOJI_ICON": "Mostrar selector de emoji",
|
"TIP_EMOJI_ICON": "Mostrar selector de emoji",
|
||||||
"TIP_ATTACH_ICON": "Adjuntar archivos",
|
"TIP_ATTACH_ICON": "Adjuntar archivos",
|
||||||
@@ -195,7 +194,6 @@
|
|||||||
},
|
},
|
||||||
"CONTEXT_MENU": {
|
"CONTEXT_MENU": {
|
||||||
"COPY": "Copiar",
|
"COPY": "Copiar",
|
||||||
"REPLY_TO": "Reply to this message",
|
|
||||||
"DELETE": "Eliminar",
|
"DELETE": "Eliminar",
|
||||||
"CREATE_A_CANNED_RESPONSE": "Añadir a respuestas predefinidas",
|
"CREATE_A_CANNED_RESPONSE": "Añadir a respuestas predefinidas",
|
||||||
"TRANSLATE": "Traducir",
|
"TRANSLATE": "Traducir",
|
||||||
|
|||||||
@@ -425,33 +425,6 @@
|
|||||||
"ERROR_MESSAGE": "No se pudo borrar la categoría"
|
"ERROR_MESSAGE": "No se pudo borrar la categoría"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"UPGRADE_PAGE": {
|
|
||||||
"TITLE": "Centro de ayuda",
|
|
||||||
"DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Upgrade your subscription to enable this feature.",
|
|
||||||
"SELF_HOSTED_DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Please contact your administrator to enable this feature.",
|
|
||||||
"BUTTON": {
|
|
||||||
"LEARN_MORE": "Más información",
|
|
||||||
"UPGRADE": "Upgrade"
|
|
||||||
},
|
|
||||||
"FEATURES": {
|
|
||||||
"PORTALS": {
|
|
||||||
"TITLE": "Multiple portals",
|
|
||||||
"DESCRIPTION": "Create multiple help center portals for different products using the same account."
|
|
||||||
},
|
|
||||||
"LOCALES": {
|
|
||||||
"TITLE": "Full support for locales",
|
|
||||||
"DESCRIPTION": "Localize the portal in your language. We support all locales and allow translations for every article."
|
|
||||||
},
|
|
||||||
"SEO": {
|
|
||||||
"TITLE": "SEO-friendly design",
|
|
||||||
"DESCRIPTION": "Customize your meta tags to improve your visibility on search engines with our SEO-friendly pages."
|
|
||||||
},
|
|
||||||
"API": {
|
|
||||||
"TITLE": "Full API support",
|
|
||||||
"DESCRIPTION": "Use the portal as a headless CMS with third party front-end frameworks using our APIs."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,7 @@
|
|||||||
"REPORTS_OVERVIEW": "Resumen",
|
"REPORTS_OVERVIEW": "Resumen",
|
||||||
"FACEBOOK_REAUTHORIZE": "Su conexión de Facebook expiró, por favor reconecte si página de Facebook para continuar con el servicio",
|
"FACEBOOK_REAUTHORIZE": "Su conexión de Facebook expiró, por favor reconecte si página de Facebook para continuar con el servicio",
|
||||||
"HELP_CENTER": {
|
"HELP_CENTER": {
|
||||||
"TITLE": "Centro de ayuda",
|
"TITLE": "Centro de ayuda (Beta)",
|
||||||
"ALL_ARTICLES": "Todos los artículos",
|
"ALL_ARTICLES": "Todos los artículos",
|
||||||
"MY_ARTICLES": "Todos los artículos",
|
"MY_ARTICLES": "Todos los artículos",
|
||||||
"DRAFT": "Borrador",
|
"DRAFT": "Borrador",
|
||||||
|
|||||||
@@ -41,16 +41,6 @@
|
|||||||
"STATUS": "وضعیت",
|
"STATUS": "وضعیت",
|
||||||
"ORDER_BY": "سفارش توسط"
|
"ORDER_BY": "سفارش توسط"
|
||||||
},
|
},
|
||||||
"CHAT_TIME_STAMP": {
|
|
||||||
"CREATED": {
|
|
||||||
"LATEST": "ایجاد شده",
|
|
||||||
"OLDEST": "ایجاد شده در:"
|
|
||||||
},
|
|
||||||
"LAST_ACTIVITY": {
|
|
||||||
"NOT_ACTIVE": "آخرین فعالیت:",
|
|
||||||
"ACTIVE": "آخرین فعالیت"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"CHAT_SORT_FILTER_ITEMS": {
|
"CHAT_SORT_FILTER_ITEMS": {
|
||||||
"latest": {
|
"latest": {
|
||||||
"TEXT": "آخرین فعالیت"
|
"TEXT": "آخرین فعالیت"
|
||||||
|
|||||||
@@ -204,10 +204,6 @@
|
|||||||
"PLACEHOLDER": "پیام خود را اینجا بنویسید",
|
"PLACEHOLDER": "پیام خود را اینجا بنویسید",
|
||||||
"ERROR": "پیام نمیتواند خالی باشد"
|
"ERROR": "پیام نمیتواند خالی باشد"
|
||||||
},
|
},
|
||||||
"ATTACHMENTS": {
|
|
||||||
"SELECT": "پروندهها را انتخاب کنید",
|
|
||||||
"HELP_TEXT": "پروندهها را اینجا بکشید و رها کنید یا پروندهها را برای پیوست انتخاب کنید"
|
|
||||||
},
|
|
||||||
"SUBMIT": "ارسال پیام",
|
"SUBMIT": "ارسال پیام",
|
||||||
"CANCEL": "انصراف",
|
"CANCEL": "انصراف",
|
||||||
"SUCCESS_MESSAGE": "پیام ارسال شد!",
|
"SUCCESS_MESSAGE": "پیام ارسال شد!",
|
||||||
@@ -347,17 +343,17 @@
|
|||||||
"DESCRIPTION": "با ادغام مخاطبین دو پروفایل به همراه تمام ویژگی ها و گفتگوها با هم ادغام می شود. در صورت تعارض، ویژگی های تماس اولیه اولویت دارد.",
|
"DESCRIPTION": "با ادغام مخاطبین دو پروفایل به همراه تمام ویژگی ها و گفتگوها با هم ادغام می شود. در صورت تعارض، ویژگی های تماس اولیه اولویت دارد.",
|
||||||
"PRIMARY": {
|
"PRIMARY": {
|
||||||
"TITLE": "مخاطب اصلی",
|
"TITLE": "مخاطب اصلی",
|
||||||
"HELP_LABEL": "حذف شود"
|
"HELP_LABEL": "نگه داشتن"
|
||||||
},
|
},
|
||||||
"PARENT": {
|
"CHILD": {
|
||||||
"TITLE": "ادغام مخاطب",
|
"TITLE": "ادغام مخاطب",
|
||||||
"PLACEHOLDER": "جستجو برای یک مخاطب",
|
"PLACEHOLDER": "جستجو برای یک مخاطب",
|
||||||
"HELP_LABEL": "نگه داشتن"
|
"HELP_LABEL": "حذف شود"
|
||||||
},
|
},
|
||||||
"SUMMARY": {
|
"SUMMARY": {
|
||||||
"TITLE": "خلاصه",
|
"TITLE": "خلاصه",
|
||||||
"DELETE_WARNING": "اطلاعات مخاطب <strong>%{primaryContactName}</strong> حذف خواهد شد.",
|
"DELETE_WARNING": "اطلاعات مخاطب <strong>%{childContactName}</strong> حذف خواهد شد.",
|
||||||
"ATTRIBUTE_WARNING": "اطلاعات مخاطب <strong>%{primaryContactName}</strong> کپی شود به <strong>%{parentContactName}</strong>."
|
"ATTRIBUTE_WARNING": "اطلاعات مخاطب <strong>%{childContactName}</strong> کپی شود به <strong>%{primaryContactName}</strong>."
|
||||||
},
|
},
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"ERROR": "ERROR_MESSAGE"
|
"ERROR": "ERROR_MESSAGE"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
"ASSIGN_TO_ME": "اختصاص به من",
|
"ASSIGN_TO_ME": "اختصاص به من",
|
||||||
"TWILIO_WHATSAPP_CAN_REPLY": "شما فقط می توانید با استفاده از یک پیام الگو به این مکالمه پاسخ دهید",
|
"TWILIO_WHATSAPP_CAN_REPLY": "شما فقط می توانید با استفاده از یک پیام الگو به این مکالمه پاسخ دهید",
|
||||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "محدودیت ۲۴ ساعته پنجره پیام",
|
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "محدودیت ۲۴ ساعته پنجره پیام",
|
||||||
|
"SELECT_A_TWEET_TO_REPLY": "لطفا برای پاسخ دادن، یک توییت را انتخاب کنید.",
|
||||||
"REPLYING_TO": "شما در حال پاسخ دادن به:",
|
"REPLYING_TO": "شما در حال پاسخ دادن به:",
|
||||||
"REMOVE_SELECTION": "حذف انتخابشدهها",
|
"REMOVE_SELECTION": "حذف انتخابشدهها",
|
||||||
"DOWNLOAD": "دانلود",
|
"DOWNLOAD": "دانلود",
|
||||||
@@ -46,7 +47,6 @@
|
|||||||
"NO_RESPONSE": "بدون پاسخ",
|
"NO_RESPONSE": "بدون پاسخ",
|
||||||
"RATING_TITLE": "رتبه",
|
"RATING_TITLE": "رتبه",
|
||||||
"FEEDBACK_TITLE": "بازخورد",
|
"FEEDBACK_TITLE": "بازخورد",
|
||||||
"REPLY_MESSAGE_NOT_FOUND": "Message not available",
|
|
||||||
"CARD": {
|
"CARD": {
|
||||||
"SHOW_LABELS": "مشاهده کردن برچسبها",
|
"SHOW_LABELS": "مشاهده کردن برچسبها",
|
||||||
"HIDE_LABELS": "پنهان کردن برچسبها"
|
"HIDE_LABELS": "پنهان کردن برچسبها"
|
||||||
@@ -139,8 +139,7 @@
|
|||||||
"PRIVATE_NOTE": "یادداشت خصوصی",
|
"PRIVATE_NOTE": "یادداشت خصوصی",
|
||||||
"SEND": "ارسال",
|
"SEND": "ارسال",
|
||||||
"CREATE": "افزودن یادداشت",
|
"CREATE": "افزودن یادداشت",
|
||||||
"DISMISS_REPLY": "Dismiss reply",
|
"TWEET": "توییت",
|
||||||
"REPLYING_TO": "Replying to:",
|
|
||||||
"TIP_FORMAT_ICON": "نمایش ویرایشگر متنی پیشرفته",
|
"TIP_FORMAT_ICON": "نمایش ویرایشگر متنی پیشرفته",
|
||||||
"TIP_EMOJI_ICON": "انتخاب ایموجی",
|
"TIP_EMOJI_ICON": "انتخاب ایموجی",
|
||||||
"TIP_ATTACH_ICON": "ضمیمه فایل",
|
"TIP_ATTACH_ICON": "ضمیمه فایل",
|
||||||
@@ -195,7 +194,6 @@
|
|||||||
},
|
},
|
||||||
"CONTEXT_MENU": {
|
"CONTEXT_MENU": {
|
||||||
"COPY": "کپی",
|
"COPY": "کپی",
|
||||||
"REPLY_TO": "Reply to this message",
|
|
||||||
"DELETE": "حذف",
|
"DELETE": "حذف",
|
||||||
"CREATE_A_CANNED_RESPONSE": "اضافه کردن به پاسخهای آماده",
|
"CREATE_A_CANNED_RESPONSE": "اضافه کردن به پاسخهای آماده",
|
||||||
"TRANSLATE": "ترجمه",
|
"TRANSLATE": "ترجمه",
|
||||||
|
|||||||
@@ -425,33 +425,6 @@
|
|||||||
"ERROR_MESSAGE": "حذف دستهبندی ممکن نیست"
|
"ERROR_MESSAGE": "حذف دستهبندی ممکن نیست"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"UPGRADE_PAGE": {
|
|
||||||
"TITLE": "مرکز راهنما",
|
|
||||||
"DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Upgrade your subscription to enable this feature.",
|
|
||||||
"SELF_HOSTED_DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Please contact your administrator to enable this feature.",
|
|
||||||
"BUTTON": {
|
|
||||||
"LEARN_MORE": "بیشتر بدانید",
|
|
||||||
"UPGRADE": "Upgrade"
|
|
||||||
},
|
|
||||||
"FEATURES": {
|
|
||||||
"PORTALS": {
|
|
||||||
"TITLE": "Multiple portals",
|
|
||||||
"DESCRIPTION": "Create multiple help center portals for different products using the same account."
|
|
||||||
},
|
|
||||||
"LOCALES": {
|
|
||||||
"TITLE": "Full support for locales",
|
|
||||||
"DESCRIPTION": "Localize the portal in your language. We support all locales and allow translations for every article."
|
|
||||||
},
|
|
||||||
"SEO": {
|
|
||||||
"TITLE": "SEO-friendly design",
|
|
||||||
"DESCRIPTION": "Customize your meta tags to improve your visibility on search engines with our SEO-friendly pages."
|
|
||||||
},
|
|
||||||
"API": {
|
|
||||||
"TITLE": "Full API support",
|
|
||||||
"DESCRIPTION": "Use the portal as a headless CMS with third party front-end frameworks using our APIs."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,7 @@
|
|||||||
"REPORTS_OVERVIEW": "بررسی اجمالی",
|
"REPORTS_OVERVIEW": "بررسی اجمالی",
|
||||||
"FACEBOOK_REAUTHORIZE": "اتصال فیس بوک شما منقضی شده است ، لطفاً برای ادامه خدمات دوباره صفحه فیس بوک خود را متصل کنید",
|
"FACEBOOK_REAUTHORIZE": "اتصال فیس بوک شما منقضی شده است ، لطفاً برای ادامه خدمات دوباره صفحه فیس بوک خود را متصل کنید",
|
||||||
"HELP_CENTER": {
|
"HELP_CENTER": {
|
||||||
"TITLE": "مرکز راهنما",
|
"TITLE": "مرکز راهنمایی (آزمایشی)",
|
||||||
"ALL_ARTICLES": "همه مقالات",
|
"ALL_ARTICLES": "همه مقالات",
|
||||||
"MY_ARTICLES": "مقالات من",
|
"MY_ARTICLES": "مقالات من",
|
||||||
"DRAFT": "پیشنویس",
|
"DRAFT": "پیشنویس",
|
||||||
|
|||||||
@@ -41,16 +41,6 @@
|
|||||||
"STATUS": "Tila",
|
"STATUS": "Tila",
|
||||||
"ORDER_BY": "Order by"
|
"ORDER_BY": "Order by"
|
||||||
},
|
},
|
||||||
"CHAT_TIME_STAMP": {
|
|
||||||
"CREATED": {
|
|
||||||
"LATEST": "Created",
|
|
||||||
"OLDEST": "Created at:"
|
|
||||||
},
|
|
||||||
"LAST_ACTIVITY": {
|
|
||||||
"NOT_ACTIVE": "Last activity:",
|
|
||||||
"ACTIVE": "Last activity"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"CHAT_SORT_FILTER_ITEMS": {
|
"CHAT_SORT_FILTER_ITEMS": {
|
||||||
"latest": {
|
"latest": {
|
||||||
"TEXT": "Last activity"
|
"TEXT": "Last activity"
|
||||||
|
|||||||
@@ -204,10 +204,6 @@
|
|||||||
"PLACEHOLDER": "Write your message here",
|
"PLACEHOLDER": "Write your message here",
|
||||||
"ERROR": "Message can't be empty"
|
"ERROR": "Message can't be empty"
|
||||||
},
|
},
|
||||||
"ATTACHMENTS": {
|
|
||||||
"SELECT": "Choose files",
|
|
||||||
"HELP_TEXT": "Drag and drop files here or choose files to attach"
|
|
||||||
},
|
|
||||||
"SUBMIT": "Lähetä viesti",
|
"SUBMIT": "Lähetä viesti",
|
||||||
"CANCEL": "Peruuta",
|
"CANCEL": "Peruuta",
|
||||||
"SUCCESS_MESSAGE": "Message sent!",
|
"SUCCESS_MESSAGE": "Message sent!",
|
||||||
@@ -347,17 +343,17 @@
|
|||||||
"DESCRIPTION": "Yhdistämällä yhteystiedot voit muuntaa kaksi profiilia yhdeksi, mukaan lukien kaikki määritteet ja keskustelut. Ristiriitatilanteessa ensisijaisen yhteyshenkilön määritteet ovat ensisijaisia.",
|
"DESCRIPTION": "Yhdistämällä yhteystiedot voit muuntaa kaksi profiilia yhdeksi, mukaan lukien kaikki määritteet ja keskustelut. Ristiriitatilanteessa ensisijaisen yhteyshenkilön määritteet ovat ensisijaisia.",
|
||||||
"PRIMARY": {
|
"PRIMARY": {
|
||||||
"TITLE": "Ensisijainen kontakti",
|
"TITLE": "Ensisijainen kontakti",
|
||||||
"HELP_LABEL": "Poistetaan"
|
"HELP_LABEL": "Säilytetään"
|
||||||
},
|
},
|
||||||
"PARENT": {
|
"CHILD": {
|
||||||
"TITLE": "Yhdistettävä yhteystieto",
|
"TITLE": "Yhdistettävä yhteystieto",
|
||||||
"PLACEHOLDER": "Etsi yhteystietoa",
|
"PLACEHOLDER": "Etsi yhteystietoa",
|
||||||
"HELP_LABEL": "Säilytetään"
|
"HELP_LABEL": "Poistetaan"
|
||||||
},
|
},
|
||||||
"SUMMARY": {
|
"SUMMARY": {
|
||||||
"TITLE": "Summary",
|
"TITLE": "Summary",
|
||||||
"DELETE_WARNING": "Contact of <strong>%{primaryContactName}</strong> will be deleted.",
|
"DELETE_WARNING": "Contact of <strong>%{childContactName}</strong> will be deleted.",
|
||||||
"ATTRIBUTE_WARNING": "Contact details of <strong>%{primaryContactName}</strong> will be copied to <strong>%{parentContactName}</strong>."
|
"ATTRIBUTE_WARNING": "Contact details of <strong>%{childContactName}</strong> will be copied to <strong>%{primaryContactName}</strong>."
|
||||||
},
|
},
|
||||||
"SEARCH": {
|
"SEARCH": {
|
||||||
"ERROR": "ERROR_MESSAGE"
|
"ERROR": "ERROR_MESSAGE"
|
||||||
|
|||||||
@@ -34,6 +34,7 @@
|
|||||||
"ASSIGN_TO_ME": "Siirrä minulle",
|
"ASSIGN_TO_ME": "Siirrä minulle",
|
||||||
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
|
"TWILIO_WHATSAPP_CAN_REPLY": "You can only reply to this conversation using a template message due to",
|
||||||
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24h vastausikkuna",
|
"TWILIO_WHATSAPP_24_HOURS_WINDOW": "24h vastausikkuna",
|
||||||
|
"SELECT_A_TWEET_TO_REPLY": "Please select a tweet to reply to.",
|
||||||
"REPLYING_TO": "Olet vastaamassa:",
|
"REPLYING_TO": "Olet vastaamassa:",
|
||||||
"REMOVE_SELECTION": "Poista valinnat",
|
"REMOVE_SELECTION": "Poista valinnat",
|
||||||
"DOWNLOAD": "Lataa",
|
"DOWNLOAD": "Lataa",
|
||||||
@@ -46,7 +47,6 @@
|
|||||||
"NO_RESPONSE": "No response",
|
"NO_RESPONSE": "No response",
|
||||||
"RATING_TITLE": "Arvio",
|
"RATING_TITLE": "Arvio",
|
||||||
"FEEDBACK_TITLE": "Palaute",
|
"FEEDBACK_TITLE": "Palaute",
|
||||||
"REPLY_MESSAGE_NOT_FOUND": "Message not available",
|
|
||||||
"CARD": {
|
"CARD": {
|
||||||
"SHOW_LABELS": "Show labels",
|
"SHOW_LABELS": "Show labels",
|
||||||
"HIDE_LABELS": "Hide labels"
|
"HIDE_LABELS": "Hide labels"
|
||||||
@@ -139,8 +139,7 @@
|
|||||||
"PRIVATE_NOTE": "Sisäinen merkintä",
|
"PRIVATE_NOTE": "Sisäinen merkintä",
|
||||||
"SEND": "Lähetä",
|
"SEND": "Lähetä",
|
||||||
"CREATE": "Lisää muistiinpano",
|
"CREATE": "Lisää muistiinpano",
|
||||||
"DISMISS_REPLY": "Dismiss reply",
|
"TWEET": "Twiittaa",
|
||||||
"REPLYING_TO": "Replying to:",
|
|
||||||
"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",
|
||||||
@@ -195,7 +194,6 @@
|
|||||||
},
|
},
|
||||||
"CONTEXT_MENU": {
|
"CONTEXT_MENU": {
|
||||||
"COPY": "Kopioi",
|
"COPY": "Kopioi",
|
||||||
"REPLY_TO": "Reply to this message",
|
|
||||||
"DELETE": "Poista",
|
"DELETE": "Poista",
|
||||||
"CREATE_A_CANNED_RESPONSE": "Add to canned responses",
|
"CREATE_A_CANNED_RESPONSE": "Add to canned responses",
|
||||||
"TRANSLATE": "Translate",
|
"TRANSLATE": "Translate",
|
||||||
|
|||||||
@@ -425,33 +425,6 @@
|
|||||||
"ERROR_MESSAGE": "Unable to delete category"
|
"ERROR_MESSAGE": "Unable to delete category"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"UPGRADE_PAGE": {
|
|
||||||
"TITLE": "Help Center",
|
|
||||||
"DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Upgrade your subscription to enable this feature.",
|
|
||||||
"SELF_HOSTED_DESCRIPTION": "Create user-friendly self-service portals. Help your users to access the articles and get support 24/7. Please contact your administrator to enable this feature.",
|
|
||||||
"BUTTON": {
|
|
||||||
"LEARN_MORE": "Learn more",
|
|
||||||
"UPGRADE": "Upgrade"
|
|
||||||
},
|
|
||||||
"FEATURES": {
|
|
||||||
"PORTALS": {
|
|
||||||
"TITLE": "Multiple portals",
|
|
||||||
"DESCRIPTION": "Create multiple help center portals for different products using the same account."
|
|
||||||
},
|
|
||||||
"LOCALES": {
|
|
||||||
"TITLE": "Full support for locales",
|
|
||||||
"DESCRIPTION": "Localize the portal in your language. We support all locales and allow translations for every article."
|
|
||||||
},
|
|
||||||
"SEO": {
|
|
||||||
"TITLE": "SEO-friendly design",
|
|
||||||
"DESCRIPTION": "Customize your meta tags to improve your visibility on search engines with our SEO-friendly pages."
|
|
||||||
},
|
|
||||||
"API": {
|
|
||||||
"TITLE": "Full API support",
|
|
||||||
"DESCRIPTION": "Use the portal as a headless CMS with third party front-end frameworks using our APIs."
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user