Compare commits
40
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14c2048c1c | ||
|
|
e8b7e791a5 | ||
|
|
a236d4da3a | ||
|
|
b18cac77fb | ||
|
|
647161121e | ||
|
|
cbbe939662 | ||
|
|
30cb141e5e | ||
|
|
b0d5c438ac | ||
|
|
3eefa4daee | ||
|
|
ddce999385 | ||
|
|
ff915dd2ea | ||
|
|
dd0930d75e | ||
|
|
3f0d96c24d | ||
|
|
c16b801562 | ||
|
|
018c468303 | ||
|
|
48bf8d08e5 | ||
|
|
fabb3c8da4 | ||
|
|
bdeb2f9812 | ||
|
|
53dc38e650 | ||
|
|
27fc24375d | ||
|
|
f999777a2d | ||
|
|
71d1e98765 | ||
|
|
2c93d563c1 | ||
|
|
f1b556d4a3 | ||
|
|
73c119cd3d | ||
|
|
0ad2f94151 | ||
|
|
f8cb806548 | ||
|
|
9ba5adfd60 | ||
|
|
0dd5104acb | ||
|
|
0968aead6c | ||
|
|
53d530b815 | ||
|
|
2429daa45c | ||
|
|
86894b27b5 | ||
|
|
05df4d4212 | ||
|
|
47fd0ea2b2 | ||
|
|
30b8a365dc | ||
|
|
29110ffd6b | ||
|
|
fe8a67b76a | ||
|
|
e204fe1c1f | ||
|
|
08fa370261 |
@@ -69,7 +69,12 @@ jobs:
|
||||
# Run Response Bot specs
|
||||
- name: Run backend tests
|
||||
run: |
|
||||
bundle exec rspec spec/enterprise/controllers/api/v1/accounts/response_sources_controller_spec.rb spec/enterprise/controllers/enterprise/api/v1/accounts/inboxes_controller_spec.rb:47 --profile=10 --format documentation
|
||||
bundle exec rspec \
|
||||
spec/enterprise/controllers/api/v1/accounts/response_sources_controller_spec.rb \
|
||||
spec/enterprise/services/enterprise/message_templates/response_bot_service_spec.rb \
|
||||
spec/enterprise/controllers/enterprise/api/v1/accounts/inboxes_controller_spec.rb:47 \
|
||||
--profile=10 \
|
||||
--format documentation
|
||||
|
||||
- name: Upload rails log folder
|
||||
uses: actions/upload-artifact@v3
|
||||
|
||||
@@ -76,6 +76,7 @@ gem 'pundit'
|
||||
# super admin
|
||||
gem 'administrate', '>= 0.19.0'
|
||||
gem 'administrate-field-active_storage'
|
||||
gem 'administrate-field-belongs_to_search'
|
||||
|
||||
##--- gems for pubsub service ---##
|
||||
# https://karolgalanciak.com/blog/2019/11/30/from-activerecord-callbacks-to-publish-slash-subscribe-pattern-and-event-driven-design/
|
||||
|
||||
@@ -116,6 +116,11 @@ GEM
|
||||
administrate-field-active_storage (0.4.2)
|
||||
administrate (>= 0.2.2)
|
||||
rails (>= 7.0)
|
||||
administrate-field-belongs_to_search (0.8.0)
|
||||
administrate (>= 0.3, < 1.0)
|
||||
jbuilder (~> 2)
|
||||
rails (>= 4.2, < 7.1)
|
||||
selectize-rails (~> 0.6)
|
||||
annotate (3.2.0)
|
||||
activerecord (>= 3.2, < 8.0)
|
||||
rake (>= 10.4, < 14.0)
|
||||
@@ -831,6 +836,7 @@ DEPENDENCIES
|
||||
acts-as-taggable-on
|
||||
administrate (>= 0.19.0)
|
||||
administrate-field-active_storage
|
||||
administrate-field-belongs_to_search
|
||||
annotate
|
||||
attr_extras
|
||||
audited (~> 5.3)
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2.17.0
|
||||
3.1.0
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
2.3.0
|
||||
2.4.0
|
||||
|
||||
@@ -29,3 +29,5 @@
|
||||
@import 'components/pagination';
|
||||
@import 'components/search';
|
||||
@import 'components/reports';
|
||||
|
||||
@import 'custom_styles';
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
// custom styles for the dashboard
|
||||
|
||||
.feature-cell {
|
||||
background: $color-extra-light-blue;
|
||||
border-radius: 10px;
|
||||
float: left;
|
||||
margin-left: 8px;
|
||||
margin-top: 6px;
|
||||
padding: 4px 12px;
|
||||
|
||||
.icon-container {
|
||||
margin-right: 4px;
|
||||
|
||||
}
|
||||
|
||||
.value-container {
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.feature-container {
|
||||
max-width: 100rem;
|
||||
}
|
||||
@@ -78,11 +78,6 @@ $conv-header-height: 4rem;
|
||||
$inbox-thumb-size: 4.8rem;
|
||||
|
||||
|
||||
// Spinner
|
||||
$spinkit-spinner-color: $color-white !default;
|
||||
$spinkit-spinner-margin: 0 0 0 1.6rem !default;
|
||||
$spinkit-size: 1.6rem !default;
|
||||
|
||||
// Snackbar default
|
||||
$woot-snackbar-bg: #323232;
|
||||
$woot-snackbar-button: #ffeb3b;
|
||||
|
||||
@@ -25,7 +25,8 @@ class Api::V1::Accounts::ContactsController < Api::V1::Accounts::BaseController
|
||||
render json: { error: 'Specify search string with parameter q' }, status: :unprocessable_entity if params[:q].blank? && return
|
||||
|
||||
contacts = resolved_contacts.where(
|
||||
'name ILIKE :search OR email ILIKE :search OR phone_number ILIKE :search OR contacts.identifier LIKE :search',
|
||||
'name ILIKE :search OR email ILIKE :search OR phone_number ILIKE :search OR contacts.identifier LIKE :search
|
||||
OR contacts.additional_attributes->>\'company_name\' ILIKE :search',
|
||||
search: "%#{params[:q].strip}%"
|
||||
)
|
||||
@contacts_count = contacts.count
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
class Api::V1::UploadController < Api::BaseController
|
||||
class Api::V1::Accounts::UploadController < Api::V1::Accounts::BaseController
|
||||
def create
|
||||
file_blob = ActiveStorage::Blob.create_and_upload!(
|
||||
key: nil,
|
||||
@@ -44,7 +44,7 @@ class Public::Api::V1::Portals::ArticlesController < Public::Api::V1::Portals::B
|
||||
end
|
||||
|
||||
def list_params
|
||||
params.permit(:query, :locale, :sort)
|
||||
params.permit(:query, :locale, :sort, :status)
|
||||
end
|
||||
|
||||
def permitted_params
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
class SlackUploadsController < ApplicationController
|
||||
include Rails.application.routes.url_helpers
|
||||
before_action :set_blob, only: [:show]
|
||||
|
||||
def show
|
||||
if @blob
|
||||
redirect_to blob_url
|
||||
else
|
||||
redirect_to avatar_url
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_blob
|
||||
@blob = ActiveStorage::Blob.find_by(key: params[:blob_key])
|
||||
end
|
||||
|
||||
def blob_url
|
||||
url_for(@blob.representation(resize_to_fill: [250, nil]))
|
||||
end
|
||||
|
||||
def avatar_url
|
||||
base_url = ENV.fetch('FRONTEND_URL', nil)
|
||||
"#{base_url}/integrations/slack/#{params[:sender_type]}.png"
|
||||
end
|
||||
end
|
||||
@@ -6,6 +6,7 @@ class SuperAdmin::InstanceStatusesController < SuperAdmin::ApplicationController
|
||||
postgres_status
|
||||
redis_metrics
|
||||
chatwoot_edition
|
||||
instance_meta
|
||||
end
|
||||
|
||||
def chatwoot_edition
|
||||
@@ -18,6 +19,10 @@ class SuperAdmin::InstanceStatusesController < SuperAdmin::ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def instance_meta
|
||||
@metrics['Database Migrations'] = ActiveRecord::Base.connection.migration_context.needs_migration? ? 'pending' : 'completed'
|
||||
end
|
||||
|
||||
def chatwoot_version
|
||||
@metrics['Chatwoot version'] = Chatwoot.config[:version]
|
||||
end
|
||||
|
||||
@@ -18,8 +18,8 @@ class AccountDashboard < Administrate::BaseDashboard
|
||||
end
|
||||
|
||||
ATTRIBUTE_TYPES = {
|
||||
id: Field::Number,
|
||||
name: Field::String,
|
||||
id: Field::Number.with_options(searchable: true),
|
||||
name: Field::String.with_options(searchable: true),
|
||||
created_at: Field::DateTime,
|
||||
updated_at: Field::DateTime,
|
||||
users: CountField,
|
||||
|
||||
@@ -8,9 +8,9 @@ class AccountUserDashboard < Administrate::BaseDashboard
|
||||
# which determines how the attribute is displayed
|
||||
# on pages throughout the dashboard.
|
||||
ATTRIBUTE_TYPES = {
|
||||
account: Field::BelongsTo.with_options(searchable: true, searchable_field: 'name', order: 'id DESC'),
|
||||
user: Field::BelongsTo.with_options(searchable: true, searchable_field: 'name', order: 'id DESC'),
|
||||
inviter: Field::BelongsTo.with_options(class_name: 'User', searchable: true, searchable_field: 'name'),
|
||||
account: Field::BelongsToSearch.with_options(class_name: 'Account', searchable: true, searchable_field: [:name, :id], order: 'id DESC'),
|
||||
user: Field::BelongsToSearch.with_options(class_name: 'User', searchable: true, searchable_field: [:name, :email, :id], order: 'id DESC'),
|
||||
inviter: Field::BelongsToSearch.with_options(class_name: 'User', searchable: true, searchable_field: [:name, :email, :id], order: 'id DESC'),
|
||||
id: Field::Number,
|
||||
role: Field::Select.with_options(collection: AccountUser.roles.keys),
|
||||
created_at: Field::DateTime,
|
||||
|
||||
@@ -9,7 +9,7 @@ class UserDashboard < Administrate::BaseDashboard
|
||||
# on pages throughout the dashboard.
|
||||
ATTRIBUTE_TYPES = {
|
||||
account_users: Field::HasMany,
|
||||
id: Field::Number,
|
||||
id: Field::Number.with_options(searchable: true),
|
||||
avatar_url: AvatarField,
|
||||
avatar: Field::ActiveStorage.with_options(
|
||||
destroy_url: proc do |_namespace, _resource, attachment|
|
||||
@@ -28,9 +28,9 @@ class UserDashboard < Administrate::BaseDashboard
|
||||
confirmed_at: Field::DateTime,
|
||||
confirmation_sent_at: Field::DateTime,
|
||||
unconfirmed_email: Field::String,
|
||||
name: Field::String,
|
||||
name: Field::String.with_options(searchable: true),
|
||||
display_name: Field::String,
|
||||
email: Field::String,
|
||||
email: Field::String.with_options(searchable: true),
|
||||
tokens: Field::String.with_options(searchable: false),
|
||||
created_at: Field::DateTime,
|
||||
updated_at: Field::DateTime,
|
||||
|
||||
@@ -26,14 +26,14 @@
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import AddAccountModal from '../dashboard/components/layout/sidebarComponents/AddAccountModal';
|
||||
import AddAccountModal from '../dashboard/components/layout/sidebarComponents/AddAccountModal.vue';
|
||||
import LoadingState from './components/widgets/LoadingState.vue';
|
||||
import NetworkNotification from './components/NetworkNotification';
|
||||
import NetworkNotification from './components/NetworkNotification.vue';
|
||||
import UpdateBanner from './components/app/UpdateBanner.vue';
|
||||
import UpgradeBanner from './components/app/UpgradeBanner.vue';
|
||||
import PaymentPendingBanner from './components/app/PaymentPendingBanner.vue';
|
||||
import vueActionCable from './helper/actionCable';
|
||||
import WootSnackbarBox from './components/SnackbarContainer';
|
||||
import WootSnackbarBox from './components/SnackbarContainer.vue';
|
||||
import rtlMixin from 'shared/mixins/rtlMixin';
|
||||
import { setColorTheme } from './helper/themeHelper';
|
||||
import {
|
||||
|
||||
@@ -81,11 +81,6 @@ $conv-header-height: 2.5rem;
|
||||
$inbox-thumb-size: 3rem;
|
||||
|
||||
|
||||
// Spinner
|
||||
$spinkit-spinner-color: $color-white !default;
|
||||
$spinkit-spinner-margin: 0 0 0 1rem !default;
|
||||
$spinkit-size: 1rem !default;
|
||||
|
||||
// Snackbar default
|
||||
$woot-snackbar-bg: #323232;
|
||||
$woot-snackbar-button: #ffeb3b;
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
@import 'shared/assets/stylesheets/border-radius';
|
||||
@import 'variables';
|
||||
|
||||
@import '~spinkit/scss/spinners/7-three-bounce';
|
||||
@import 'vue-multiselect/dist/vue-multiselect.min.css';
|
||||
@import '~shared/assets/stylesheets/ionicons';
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import EmojiOrIcon from 'shared/components/EmojiOrIcon';
|
||||
import EmojiOrIcon from 'shared/components/EmojiOrIcon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -193,17 +193,17 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import ConversationAdvancedFilter from './widgets/conversation/ConversationAdvancedFilter';
|
||||
import ConversationBasicFilter from './widgets/conversation/ConversationBasicFilter';
|
||||
import ChatTypeTabs from './widgets/ChatTypeTabs';
|
||||
import ConversationCard from './widgets/conversation/ConversationCard';
|
||||
import ConversationAdvancedFilter from './widgets/conversation/ConversationAdvancedFilter.vue';
|
||||
import ConversationBasicFilter from './widgets/conversation/ConversationBasicFilter.vue';
|
||||
import ChatTypeTabs from './widgets/ChatTypeTabs.vue';
|
||||
import ConversationCard from './widgets/conversation/ConversationCard.vue';
|
||||
import timeMixin from '../mixins/time';
|
||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||
import conversationMixin from '../mixins/conversations';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import advancedFilterTypes from './widgets/conversation/advancedFilterItems';
|
||||
import filterQueryGenerator from '../helper/filterQueryGenerator.js';
|
||||
import AddCustomViews from 'dashboard/routes/dashboard/customviews/AddCustomViews';
|
||||
import AddCustomViews from 'dashboard/routes/dashboard/customviews/AddCustomViews.vue';
|
||||
import DeleteCustomViews from 'dashboard/routes/dashboard/customviews/DeleteCustomViews.vue';
|
||||
import ConversationBulkActions from './widgets/conversation/conversationBulkActions/Index.vue';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import WootSnackbar from './Snackbar';
|
||||
import WootSnackbar from './Snackbar.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Spinner from 'shared/components/Spinner';
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -90,7 +90,7 @@ import { getUnixTime } from 'date-fns';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal';
|
||||
import CustomSnoozeModal from 'dashboard/components/CustomSnoozeModal.vue';
|
||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||
import {
|
||||
hasPressedAltAndEKey,
|
||||
|
||||
@@ -49,11 +49,11 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem';
|
||||
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu';
|
||||
import WootDropdownHeader from 'shared/components/ui/dropdown/DropdownHeader';
|
||||
import WootDropdownDivider from 'shared/components/ui/dropdown/DropdownDivider';
|
||||
import AvailabilityStatusBadge from '../widgets/conversation/AvailabilityStatusBadge';
|
||||
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
||||
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
||||
import WootDropdownHeader from 'shared/components/ui/dropdown/DropdownHeader.vue';
|
||||
import WootDropdownDivider from 'shared/components/ui/dropdown/DropdownDivider.vue';
|
||||
import AvailabilityStatusBadge from '../widgets/conversation/AvailabilityStatusBadge.vue';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
|
||||
const { AVAILABILITY_STATUS_KEYS } = wootConstants;
|
||||
|
||||
@@ -35,8 +35,8 @@ import adminMixin from '../../mixins/isAdmin';
|
||||
import { getSidebarItems } from './config/default-sidebar';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
|
||||
import PrimarySidebar from './sidebarComponents/Primary';
|
||||
import SecondarySidebar from './sidebarComponents/Secondary';
|
||||
import PrimarySidebar from './sidebarComponents/Primary.vue';
|
||||
import SecondarySidebar from './sidebarComponents/Secondary.vue';
|
||||
import {
|
||||
hasPressedAltAndCKey,
|
||||
hasPressedAltAndRKey,
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import Thumbnail from '../../widgets/Thumbnail';
|
||||
import Thumbnail from '../../widgets/Thumbnail.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -109,9 +109,9 @@
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import { mapGetters } from 'vuex';
|
||||
import Auth from '../../../api/auth';
|
||||
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem';
|
||||
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu';
|
||||
import AvailabilityStatus from 'dashboard/components/layout/AvailabilityStatus';
|
||||
import WootDropdownItem from 'shared/components/ui/dropdown/DropdownItem.vue';
|
||||
import WootDropdownMenu from 'shared/components/ui/dropdown/DropdownMenu.vue';
|
||||
import AvailabilityStatus from 'dashboard/components/layout/AvailabilityStatus.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -39,11 +39,11 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Logo from './Logo';
|
||||
import PrimaryNavItem from './PrimaryNavItem';
|
||||
import OptionsMenu from './OptionsMenu';
|
||||
import AgentDetails from './AgentDetails';
|
||||
import NotificationBell from './NotificationBell';
|
||||
import Logo from './Logo.vue';
|
||||
import PrimaryNavItem from './PrimaryNavItem.vue';
|
||||
import OptionsMenu from './OptionsMenu.vue';
|
||||
import AgentDetails from './AgentDetails.vue';
|
||||
import NotificationBell from './NotificationBell.vue';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { frontendURL } from 'dashboard/helper/URLHelper';
|
||||
import { ACCOUNT_EVENTS } from '../../../helper/AnalyticsHelper/events';
|
||||
|
||||
@@ -97,7 +97,7 @@ import {
|
||||
getInboxWarningIconClass,
|
||||
} from 'dashboard/helper/inbox';
|
||||
|
||||
import SecondaryChildNavItem from './SecondaryChildNavItem';
|
||||
import SecondaryChildNavItem from './SecondaryChildNavItem.vue';
|
||||
import {
|
||||
isOnMentionsView,
|
||||
isOnUnattendedView,
|
||||
|
||||
@@ -28,8 +28,8 @@
|
||||
</button>
|
||||
</template>
|
||||
<script>
|
||||
import Spinner from 'shared/components/Spinner';
|
||||
import EmojiOrIcon from 'shared/components/EmojiOrIcon';
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import EmojiOrIcon from 'shared/components/EmojiOrIcon.vue';
|
||||
|
||||
export default {
|
||||
name: 'WootButton',
|
||||
|
||||
@@ -108,7 +108,7 @@
|
||||
<script>
|
||||
import AutomationActionTeamMessageInput from './AutomationActionTeamMessageInput.vue';
|
||||
import AutomationActionFileInput from './AutomationFileInput.vue';
|
||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor';
|
||||
import WootMessageEditor from 'dashboard/components/widgets/WootWriter/Editor.vue';
|
||||
export default {
|
||||
components: {
|
||||
AutomationActionTeamMessageInput,
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Spinner from 'shared/components/Spinner';
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
/>
|
||||
</template>
|
||||
<script>
|
||||
import ChannelSelector from '../ChannelSelector';
|
||||
import ChannelSelector from '../ChannelSelector.vue';
|
||||
export default {
|
||||
components: { ChannelSelector },
|
||||
props: {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import LoadingState from 'dashboard/components/widgets/LoadingState';
|
||||
import LoadingState from 'dashboard/components/widgets/LoadingState.vue';
|
||||
export default {
|
||||
components: {
|
||||
LoadingState,
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import AddLabel from 'shared/components/ui/dropdown/AddLabel';
|
||||
import AddLabel from 'shared/components/ui/dropdown/AddLabel.vue';
|
||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||
import LabelDropdown from 'shared/components/ui/label/LabelDropdown';
|
||||
import LabelDropdown from 'shared/components/ui/label/LabelDropdown.vue';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import adminMixin from 'dashboard/mixins/isAdmin';
|
||||
import {
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
* Badge - Chat source indication { fb / telegram }
|
||||
* Username - Username for avatar
|
||||
*/
|
||||
import Avatar from './Avatar';
|
||||
import Avatar from './Avatar.vue';
|
||||
import { removeEmoji } from 'shared/helpers/emoji';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import Thumbnail from './Thumbnail';
|
||||
import Thumbnail from './Thumbnail.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -41,9 +41,13 @@ import {
|
||||
triggerCharacters,
|
||||
} from '@chatwoot/prosemirror-schema/src/mentions/plugin';
|
||||
|
||||
import TagAgents from '../conversation/TagAgents';
|
||||
import CannedResponse from '../conversation/CannedResponse';
|
||||
import VariableList from '../conversation/VariableList';
|
||||
import TagAgents from '../conversation/TagAgents.vue';
|
||||
import CannedResponse from '../conversation/CannedResponse.vue';
|
||||
import VariableList from '../conversation/VariableList.vue';
|
||||
import {
|
||||
appendSignature,
|
||||
removeSignature,
|
||||
} from 'dashboard/helper/editorHelper';
|
||||
|
||||
const TYPING_INDICATOR_IDLE_TIME = 4000;
|
||||
const MAXIMUM_FILE_UPLOAD_SIZE = 4; // in MB
|
||||
@@ -100,6 +104,10 @@ export default {
|
||||
enableCannedResponses: { type: Boolean, default: true },
|
||||
variables: { type: Object, default: () => ({}) },
|
||||
enabledMenuOptions: { type: Array, default: () => [] },
|
||||
signature: { type: String, default: '' },
|
||||
// allowSignature is a kill switch, ensuring no signature methods
|
||||
// are triggered except when this flag is true
|
||||
allowSignature: { type: Boolean, default: false },
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -220,6 +228,12 @@ export default {
|
||||
}),
|
||||
];
|
||||
},
|
||||
sendWithSignature() {
|
||||
// this is considered the source of truth, we watch this property
|
||||
// on change, we toggle the signature in the editor
|
||||
const { send_with_signature: isEnabled } = this.uiSettings;
|
||||
return isEnabled && this.allowSignature && !this.isPrivate;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
showUserMentions(updatedValue) {
|
||||
@@ -244,7 +258,6 @@ export default {
|
||||
isPrivate() {
|
||||
this.reloadState(this.value);
|
||||
},
|
||||
|
||||
updateSelectionWith(newValue, oldValue) {
|
||||
if (!this.editorView) {
|
||||
return null;
|
||||
@@ -263,6 +276,12 @@ export default {
|
||||
}
|
||||
return null;
|
||||
},
|
||||
sendWithSignature(newValue) {
|
||||
// see if the allowSignature flag is true
|
||||
if (this.allowSignature) {
|
||||
this.toggleSignatureInEditor(newValue);
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.state = createState(
|
||||
@@ -276,7 +295,7 @@ export default {
|
||||
mounted() {
|
||||
this.createEditorView();
|
||||
this.editorView.updateState(this.state);
|
||||
this.focusEditorInputField();
|
||||
this.focusEditor(this.value);
|
||||
},
|
||||
methods: {
|
||||
reloadState(content = this.value) {
|
||||
@@ -288,7 +307,75 @@ export default {
|
||||
this.editorMenuOptions
|
||||
);
|
||||
this.editorView.updateState(this.state);
|
||||
this.focusEditorInputField();
|
||||
|
||||
this.focusEditor(content);
|
||||
},
|
||||
focusEditor(content) {
|
||||
if (this.isBodyEmpty(content) && this.sendWithSignature) {
|
||||
// reload state can be called when switching between conversations, or when drafts is loaded
|
||||
// these drafts can also have a signature, so we need to check if the body is empty
|
||||
// and handle things accordingly
|
||||
this.handleEmptyBodyWithSignature();
|
||||
} else {
|
||||
// this is in the else block, handleEmptyBodyWithSignature also has a call to the focus method
|
||||
// the position is set to start, because the signature is added at the end of the body
|
||||
this.focusEditorInputField('end');
|
||||
}
|
||||
},
|
||||
toggleSignatureInEditor(signatureEnabled) {
|
||||
// The toggleSignatureInEditor gets the new value from the
|
||||
// watcher, this means that if the value is true, the signature
|
||||
// is supposed to be added, else we remove it.
|
||||
if (signatureEnabled) {
|
||||
this.addSignature();
|
||||
} else {
|
||||
this.removeSignature();
|
||||
}
|
||||
},
|
||||
addSignature() {
|
||||
let content = this.value;
|
||||
// see if the content is empty, if it is before appending the signature
|
||||
// we need to add a paragraph node and move the cursor at the start of the editor
|
||||
const contentWasEmpty = this.isBodyEmpty(content);
|
||||
content = appendSignature(content, this.signature);
|
||||
// need to reload first, ensuring that the editorView is updated
|
||||
this.reloadState(content);
|
||||
|
||||
if (contentWasEmpty) {
|
||||
this.handleEmptyBodyWithSignature();
|
||||
}
|
||||
},
|
||||
removeSignature() {
|
||||
if (!this.signature) return;
|
||||
let content = this.value;
|
||||
content = removeSignature(content, this.signature);
|
||||
// reload the state, ensuring that the editorView is updated
|
||||
this.reloadState(content);
|
||||
},
|
||||
isBodyEmpty(content) {
|
||||
// if content is undefined, we assume that the body is empty
|
||||
if (!content) return true;
|
||||
|
||||
// if the signature is present, we need to remove it before checking
|
||||
// note that we don't update the editorView, so this is safe
|
||||
const bodyWithoutSignature = this.signature
|
||||
? removeSignature(content, this.signature)
|
||||
: content;
|
||||
|
||||
// trimming should remove all the whitespaces, so we can check the length
|
||||
return bodyWithoutSignature.trim().length === 0;
|
||||
},
|
||||
handleEmptyBodyWithSignature() {
|
||||
const { schema, tr } = this.state;
|
||||
|
||||
// create a paragraph node and
|
||||
// start a transaction to append it at the end
|
||||
const paragraph = schema.nodes.paragraph.create();
|
||||
const paragraphTransaction = tr.insert(0, paragraph);
|
||||
this.editorView.dispatch(paragraphTransaction);
|
||||
|
||||
// Set the focus at the start of the input field
|
||||
this.focusEditorInputField('start');
|
||||
},
|
||||
createEditorView() {
|
||||
this.editorView = new EditorView(this.$refs.editor, {
|
||||
@@ -333,9 +420,11 @@ export default {
|
||||
this.focusEditorInputField();
|
||||
}
|
||||
},
|
||||
focusEditorInputField() {
|
||||
focusEditorInputField(pos = 'end') {
|
||||
const { tr } = this.editorView.state;
|
||||
const selection = Selection.atEnd(tr.doc);
|
||||
|
||||
const selection =
|
||||
pos === 'end' ? Selection.atEnd(tr.doc) : Selection.atStart(tr.doc);
|
||||
|
||||
this.editorView.dispatch(tr.setSelection(selection));
|
||||
this.editorView.focus();
|
||||
@@ -581,6 +670,10 @@ export default {
|
||||
|
||||
.ProseMirror-prompt {
|
||||
@apply z-50 bg-slate-25 dark:bg-slate-700 rounded-md border border-solid border-slate-75 dark:border-slate-800;
|
||||
|
||||
h5 {
|
||||
@apply dark:text-slate-25 text-slate-800;
|
||||
}
|
||||
}
|
||||
|
||||
.is-private {
|
||||
|
||||
@@ -134,7 +134,7 @@ import {
|
||||
ALLOWED_FILE_TYPES,
|
||||
ALLOWED_FILE_TYPES_FOR_TWILIO_WHATSAPP,
|
||||
} from 'shared/constants/messages';
|
||||
import VideoCallButton from '../VideoCallButton';
|
||||
import VideoCallButton from '../VideoCallButton.vue';
|
||||
import AIAssistanceButton from '../AIAssistanceButton.vue';
|
||||
import { REPLY_EDITOR_MODES } from './constants';
|
||||
import { mapGetters } from 'vuex';
|
||||
@@ -288,7 +288,7 @@ export default {
|
||||
}
|
||||
},
|
||||
showMessageSignatureButton() {
|
||||
return !this.isOnPrivateNote && this.isAnEmailChannel;
|
||||
return !this.isOnPrivateNote;
|
||||
},
|
||||
sendWithSignature() {
|
||||
const { send_with_signature: isEnabled } = this.uiSettings;
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import FilterItem from './FilterItem';
|
||||
import FilterItem from './FilterItem.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -58,11 +58,11 @@
|
||||
</template>
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import ContactPanel from 'dashboard/routes/dashboard/conversation/ContactPanel';
|
||||
import ConversationHeader from './ConversationHeader';
|
||||
import ContactPanel from 'dashboard/routes/dashboard/conversation/ContactPanel.vue';
|
||||
import ConversationHeader from './ConversationHeader.vue';
|
||||
import DashboardAppFrame from '../DashboardApp/Frame.vue';
|
||||
import EmptyState from './EmptyState/EmptyState';
|
||||
import MessagesView from './MessagesView';
|
||||
import EmptyState from './EmptyState/EmptyState.vue';
|
||||
import MessagesView from './MessagesView.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -147,16 +147,16 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||
import Thumbnail from '../Thumbnail';
|
||||
import Thumbnail from '../Thumbnail.vue';
|
||||
import conversationMixin from '../../../mixins/conversations';
|
||||
import timeMixin from '../../../mixins/time';
|
||||
import router from '../../../routes';
|
||||
import { frontendURL, conversationUrl } from '../../../helper/URLHelper';
|
||||
import InboxName from '../InboxName';
|
||||
import InboxName from '../InboxName.vue';
|
||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||
import ConversationContextMenu from './contextMenu/Index.vue';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import TimeAgo from 'dashboard/components/ui/TimeAgo';
|
||||
import TimeAgo from 'dashboard/components/ui/TimeAgo.vue';
|
||||
import CardLabels from './conversationCardComponents/CardLabels.vue';
|
||||
import PriorityMark from './PriorityMark.vue';
|
||||
const ATTACHMENT_ICONS = {
|
||||
|
||||
@@ -73,12 +73,12 @@
|
||||
import { hasPressedAltAndOKey } from 'shared/helpers/KeyboardHelpers';
|
||||
import { mapGetters } from 'vuex';
|
||||
import agentMixin from '../../../mixins/agentMixin.js';
|
||||
import BackButton from '../BackButton';
|
||||
import BackButton from '../BackButton.vue';
|
||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||
import InboxName from '../InboxName';
|
||||
import MoreActions from './MoreActions';
|
||||
import Thumbnail from '../Thumbnail';
|
||||
import InboxName from '../InboxName.vue';
|
||||
import MoreActions from './MoreActions.vue';
|
||||
import Thumbnail from '../Thumbnail.vue';
|
||||
import wootConstants from 'dashboard/constants/globals';
|
||||
import { conversationListPageURL } from 'dashboard/helper/URLHelper';
|
||||
import { conversationReopenTime } from 'dashboard/helper/snoozeHelpers';
|
||||
|
||||
@@ -37,8 +37,8 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
import adminMixin from 'dashboard/mixins/isAdmin';
|
||||
import accountMixin from 'dashboard/mixins/account';
|
||||
import OnboardingView from '../OnboardingView';
|
||||
import EmptyStateMessage from './EmptyStateMessage';
|
||||
import OnboardingView from '../OnboardingView.vue';
|
||||
import EmptyStateMessage from './EmptyStateMessage.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Hotkey from 'dashboard/components/base/Hotkey';
|
||||
import Hotkey from 'dashboard/components/base/Hotkey.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -130,18 +130,18 @@
|
||||
</template>
|
||||
<script>
|
||||
import messageFormatterMixin from 'shared/mixins/messageFormatterMixin';
|
||||
import BubbleActions from './bubble/Actions';
|
||||
import BubbleFile from './bubble/File';
|
||||
import BubbleImage from './bubble/Image';
|
||||
import BubbleVideo from './bubble/Video';
|
||||
import BubbleImageAudioVideo from './bubble/ImageAudioVideo';
|
||||
import BubbleActions from './bubble/Actions.vue';
|
||||
import BubbleFile from './bubble/File.vue';
|
||||
import BubbleImage from './bubble/Image.vue';
|
||||
import BubbleVideo from './bubble/Video.vue';
|
||||
import BubbleImageAudioVideo from './bubble/ImageAudioVideo.vue';
|
||||
import BubbleIntegration from './bubble/Integration.vue';
|
||||
import BubbleLocation from './bubble/Location';
|
||||
import BubbleMailHead from './bubble/MailHead';
|
||||
import BubbleText from './bubble/Text';
|
||||
import BubbleContact from './bubble/Contact';
|
||||
import Spinner from 'shared/components/Spinner';
|
||||
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu';
|
||||
import BubbleLocation from './bubble/Location.vue';
|
||||
import BubbleMailHead from './bubble/MailHead.vue';
|
||||
import BubbleText from './bubble/Text.vue';
|
||||
import BubbleContact from './bubble/Contact.vue';
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import ContextMenu from 'dashboard/modules/conversations/components/MessageContextMenu.vue';
|
||||
import instagramImageErrorPlaceholder from './instagramImageErrorPlaceholder.vue';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import contentTypeMixin from 'shared/mixins/contentTypeMixin';
|
||||
|
||||
@@ -98,9 +98,9 @@
|
||||
|
||||
<script>
|
||||
// components
|
||||
import ReplyBox from './ReplyBox';
|
||||
import Message from './Message';
|
||||
import ConversationLabelSuggestion from './conversation/LabelSuggestion';
|
||||
import ReplyBox from './ReplyBox.vue';
|
||||
import Message from './Message.vue';
|
||||
import ConversationLabelSuggestion from './conversation/LabelSuggestion.vue';
|
||||
import Banner from 'dashboard/components/ui/Banner.vue';
|
||||
|
||||
// stores and apis
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
import EmailTranscriptModal from './EmailTranscriptModal';
|
||||
import ResolveAction from '../../buttons/ResolveAction';
|
||||
import EmailTranscriptModal from './EmailTranscriptModal.vue';
|
||||
import ResolveAction from '../../buttons/ResolveAction.vue';
|
||||
import {
|
||||
CMD_MUTE_CONVERSATION,
|
||||
CMD_SEND_TRANSCRIPT,
|
||||
|
||||
@@ -53,6 +53,9 @@
|
||||
class="input"
|
||||
:placeholder="messagePlaceHolder"
|
||||
:min-height="4"
|
||||
:signature="signatureToApply"
|
||||
:allow-signature="true"
|
||||
:send-with-signature="sendWithSignature"
|
||||
@typing-off="onTypingOff"
|
||||
@typing-on="onTypingOn"
|
||||
@focus="onFocus"
|
||||
@@ -69,6 +72,8 @@
|
||||
:min-height="4"
|
||||
:enable-variables="true"
|
||||
:variables="messageVariables"
|
||||
:signature="signatureToApply"
|
||||
:allow-signature="true"
|
||||
@typing-off="onTypingOff"
|
||||
@typing-on="onTypingOn"
|
||||
@focus="onFocus"
|
||||
@@ -86,16 +91,10 @@
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="isSignatureEnabledForInbox"
|
||||
v-tooltip="$t('CONVERSATION.FOOTER.MESSAGE_SIGN_TOOLTIP')"
|
||||
v-if="isSignatureEnabledForInbox && !isSignatureAvailable"
|
||||
class="message-signature-wrap"
|
||||
>
|
||||
<p
|
||||
v-if="isSignatureAvailable"
|
||||
v-dompurify-html="formatMessage(messageSignature)"
|
||||
class="message-signature"
|
||||
/>
|
||||
<p v-else class="message-signature">
|
||||
<p class="message-signature">
|
||||
{{ $t('CONVERSATION.FOOTER.MESSAGE_SIGNATURE_NOT_CONFIGURED') }}
|
||||
<router-link :to="profilePath">
|
||||
{{ $t('CONVERSATION.FOOTER.CLICK_HERE') }}
|
||||
@@ -149,23 +148,18 @@ import { mapGetters } from 'vuex';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import alertMixin from 'shared/mixins/alertMixin';
|
||||
|
||||
import CannedResponse from './CannedResponse';
|
||||
import ResizableTextArea from 'shared/components/ResizableTextArea';
|
||||
import AttachmentPreview from 'dashboard/components/widgets/AttachmentsPreview';
|
||||
import ReplyTopPanel from 'dashboard/components/widgets/WootWriter/ReplyTopPanel';
|
||||
import ReplyEmailHead from './ReplyEmailHead';
|
||||
import ReplyBottomPanel from 'dashboard/components/widgets/WootWriter/ReplyBottomPanel';
|
||||
import CannedResponse from './CannedResponse.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 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';
|
||||
import WootAudioRecorder from 'dashboard/components/widgets/WootWriter/AudioRecorder';
|
||||
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 { checkFileSizeLimit } from 'shared/helpers/FileHelper';
|
||||
import {
|
||||
MAXIMUM_FILE_UPLOAD_SIZE,
|
||||
MAXIMUM_FILE_UPLOAD_SIZE_TWILIO_SMS_CHANNEL,
|
||||
AUDIO_FORMATS,
|
||||
} from 'shared/constants/messages';
|
||||
import { AUDIO_FORMATS } from 'shared/constants/messages';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import {
|
||||
getMessageVariables,
|
||||
@@ -177,13 +171,19 @@ import { buildHotKeys } from 'shared/helpers/KeyboardHelpers';
|
||||
import { MESSAGE_MAX_LENGTH } from 'shared/helpers/MessageTypeHelper';
|
||||
import inboxMixin from 'shared/mixins/inboxMixin';
|
||||
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||
import { DirectUpload } from 'activestorage';
|
||||
import { frontendURL } from '../../../helper/URLHelper';
|
||||
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 fileUploadMixin from 'dashboard/mixins/fileUploadMixin';
|
||||
import {
|
||||
appendSignature,
|
||||
removeSignature,
|
||||
replaceSignature,
|
||||
extractTextFromMarkdown,
|
||||
} from 'dashboard/helper/editorHelper';
|
||||
|
||||
const EmojiInput = () => import('shared/components/emoji/EmojiInput');
|
||||
|
||||
@@ -208,6 +208,7 @@ export default {
|
||||
alertMixin,
|
||||
messageFormatterMixin,
|
||||
rtlMixin,
|
||||
fileUploadMixin,
|
||||
],
|
||||
props: {
|
||||
selectedTweet: {
|
||||
@@ -471,10 +472,10 @@ export default {
|
||||
);
|
||||
},
|
||||
isSignatureEnabledForInbox() {
|
||||
return !this.isPrivate && this.isAnEmailChannel && this.sendWithSignature;
|
||||
return !this.isPrivate && this.sendWithSignature;
|
||||
},
|
||||
isSignatureAvailable() {
|
||||
return !!this.messageSignature;
|
||||
return !!this.signatureToApply;
|
||||
},
|
||||
sendWithSignature() {
|
||||
const { send_with_signature: isEnabled } = this.uiSettings;
|
||||
@@ -514,6 +515,12 @@ export default {
|
||||
});
|
||||
return variables;
|
||||
},
|
||||
// ensure that the signature is plain text depending on `showRichContentEditor`
|
||||
signatureToApply() {
|
||||
return this.showRichContentEditor
|
||||
? this.messageSignature
|
||||
: extractTextFromMarkdown(this.messageSignature);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
currentChat(conversation) {
|
||||
@@ -581,6 +588,23 @@ export default {
|
||||
this.updateUISettings({
|
||||
display_rich_content_editor: !this.showRichContentEditor,
|
||||
});
|
||||
|
||||
const plainTextSignature = extractTextFromMarkdown(this.messageSignature);
|
||||
|
||||
if (!this.showRichContentEditor && this.messageSignature) {
|
||||
// remove the old signature -> extract text from markdown -> attach new signature
|
||||
let message = removeSignature(this.message, this.messageSignature);
|
||||
message = extractTextFromMarkdown(message);
|
||||
message = appendSignature(message, plainTextSignature);
|
||||
|
||||
this.message = message;
|
||||
} else {
|
||||
this.message = replaceSignature(
|
||||
this.message,
|
||||
plainTextSignature,
|
||||
this.messageSignature
|
||||
);
|
||||
}
|
||||
},
|
||||
saveDraft(conversationId, replyType) {
|
||||
if (this.message || this.message === '') {
|
||||
@@ -600,9 +624,22 @@ export default {
|
||||
getFromDraft() {
|
||||
if (this.conversationIdByRoute) {
|
||||
const key = `draft-${this.conversationIdByRoute}-${this.replyType}`;
|
||||
this.message = this.$store.getters['draftMessages/get'](key) || '';
|
||||
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) {
|
||||
if (this.isPrivate) {
|
||||
return message;
|
||||
}
|
||||
|
||||
return this.sendWithSignature
|
||||
? appendSignature(message, this.signatureToApply)
|
||||
: removeSignature(message, this.signatureToApply);
|
||||
},
|
||||
removeFromDraft() {
|
||||
if (this.conversationIdByRoute) {
|
||||
const key = `draft-${this.conversationIdByRoute}-${this.replyType}`;
|
||||
@@ -694,19 +731,14 @@ export default {
|
||||
return;
|
||||
}
|
||||
if (!this.showMentions) {
|
||||
let newMessage = this.message;
|
||||
if (this.isSignatureEnabledForInbox && this.messageSignature) {
|
||||
newMessage += '\n\n' + this.messageSignature;
|
||||
}
|
||||
|
||||
const isOnWhatsApp =
|
||||
this.isATwilioWhatsAppChannel ||
|
||||
this.isAWhatsAppCloudChannel ||
|
||||
this.is360DialogWhatsAppChannel;
|
||||
if (isOnWhatsApp && !this.isPrivate) {
|
||||
this.sendMessageAsMultipleMessages(newMessage);
|
||||
this.sendMessageAsMultipleMessages(this.message);
|
||||
} else {
|
||||
const messagePayload = this.getMessagePayload(newMessage);
|
||||
const messagePayload = this.getMessagePayload(this.message);
|
||||
this.sendMessage(messagePayload);
|
||||
}
|
||||
|
||||
@@ -828,6 +860,10 @@ export default {
|
||||
},
|
||||
clearMessage() {
|
||||
this.message = '';
|
||||
if (this.sendWithSignature && !this.isPrivate) {
|
||||
// if signature is enabled, append it to the message
|
||||
this.message = appendSignature(this.message, this.signatureToApply);
|
||||
}
|
||||
this.attachedFiles = [];
|
||||
this.isRecordingAudio = false;
|
||||
},
|
||||
@@ -904,67 +940,6 @@ export default {
|
||||
isPrivate,
|
||||
});
|
||||
},
|
||||
onFileUpload(file) {
|
||||
if (this.globalConfig.directUploadsEnabled) {
|
||||
this.onDirectFileUpload(file);
|
||||
} else {
|
||||
this.onIndirectFileUpload(file);
|
||||
}
|
||||
},
|
||||
onDirectFileUpload(file) {
|
||||
const MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE = this.isATwilioSMSChannel
|
||||
? MAXIMUM_FILE_UPLOAD_SIZE_TWILIO_SMS_CHANNEL
|
||||
: MAXIMUM_FILE_UPLOAD_SIZE;
|
||||
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
if (checkFileSizeLimit(file, MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE)) {
|
||||
const upload = new DirectUpload(
|
||||
file.file,
|
||||
`/api/v1/accounts/${this.accountId}/conversations/${this.currentChat.id}/direct_uploads`,
|
||||
{
|
||||
directUploadWillCreateBlobWithXHR: xhr => {
|
||||
xhr.setRequestHeader(
|
||||
'api_access_token',
|
||||
this.currentUser.access_token
|
||||
);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
upload.create((error, blob) => {
|
||||
if (error) {
|
||||
this.showAlert(error);
|
||||
} else {
|
||||
this.attachFile({ file, blob });
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.showAlert(
|
||||
this.$t('CONVERSATION.FILE_SIZE_LIMIT', {
|
||||
MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE,
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
onIndirectFileUpload(file) {
|
||||
const MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE = this.isATwilioSMSChannel
|
||||
? MAXIMUM_FILE_UPLOAD_SIZE_TWILIO_SMS_CHANNEL
|
||||
: MAXIMUM_FILE_UPLOAD_SIZE;
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
if (checkFileSizeLimit(file, MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE)) {
|
||||
this.attachFile({ file });
|
||||
} else {
|
||||
this.showAlert(
|
||||
this.$t('CONVERSATION.FILE_SIZE_LIMIT', {
|
||||
MAXIMUM_SUPPORTED_FILE_UPLOAD_SIZE,
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
attachFile({ blob, file }) {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file.file);
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
import { mapGetters } from 'vuex';
|
||||
import { hasPressedCommand } from 'shared/helpers/KeyboardHelpers';
|
||||
|
||||
import GalleryView from '../components/GalleryView';
|
||||
import GalleryView from '../components/GalleryView.vue';
|
||||
|
||||
const ALLOWED_FILE_TYPES = {
|
||||
IMAGE: 'image',
|
||||
|
||||
@@ -150,7 +150,7 @@ import {
|
||||
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
|
||||
import timeMixin from 'dashboard/mixins/time';
|
||||
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
|
||||
const ALLOWED_FILE_TYPES = {
|
||||
IMAGE: 'image',
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Spinner from 'shared/components/Spinner';
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
+1
-1
@@ -96,7 +96,7 @@
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||
import Spinner from 'shared/components/Spinner';
|
||||
import Spinner from 'shared/components/Spinner.vue';
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
|
||||
export default {
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
<script>
|
||||
import { required } from 'vuelidate/lib/validators';
|
||||
import Modal from '../../Modal';
|
||||
import Modal from '../../Modal.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
</modal>
|
||||
</template>
|
||||
<script>
|
||||
import Modal from '../../Modal';
|
||||
import Modal from '../../Modal.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Modal from '../../Modal';
|
||||
import Modal from '../../Modal.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
<script>
|
||||
import { mixin as clickaway } from 'vue-clickaway';
|
||||
import { SHORTCUT_KEYS } from './constants';
|
||||
import Hotkey from 'dashboard/components/base/Hotkey';
|
||||
import Hotkey from 'dashboard/components/base/Hotkey.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
import {
|
||||
messageSchema,
|
||||
MessageMarkdownTransformer,
|
||||
MessageMarkdownSerializer,
|
||||
} from '@chatwoot/prosemirror-schema';
|
||||
|
||||
/**
|
||||
* The delimiter used to separate the signature from the rest of the body.
|
||||
* @type {string}
|
||||
*/
|
||||
export const SIGNATURE_DELIMITER = '--';
|
||||
|
||||
/**
|
||||
* Parse and Serialize the markdown text to remove any extra spaces or new lines
|
||||
*/
|
||||
export function cleanSignature(signature) {
|
||||
// convert from markdown to common mark format
|
||||
const nodes = new MessageMarkdownTransformer(messageSchema).parse(signature);
|
||||
return MessageMarkdownSerializer.serialize(nodes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the signature delimiter to the beginning of the signature.
|
||||
*
|
||||
* @param {string} signature - The signature to add the delimiter to.
|
||||
* @returns {string} - The signature with the delimiter added.
|
||||
*/
|
||||
function appendDelimiter(signature) {
|
||||
return `${SIGNATURE_DELIMITER}\n\n${cleanSignature(signature)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there's an unedited signature at the end of the body
|
||||
* If there is, return the index of the signature, If there isn't, return -1
|
||||
*
|
||||
* @param {string} body - The body to search for the signature.
|
||||
* @param {string} signature - The signature to search for.
|
||||
* @returns {number} - The index of the last occurrence of the signature in the body, or -1 if not found.
|
||||
*/
|
||||
export function findSignatureInBody(body, signature) {
|
||||
const trimmedBody = body.trimEnd();
|
||||
const cleanedSignature = cleanSignature(signature);
|
||||
|
||||
// check if body ends with signature
|
||||
if (trimmedBody.endsWith(cleanedSignature)) {
|
||||
return body.lastIndexOf(cleanedSignature);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends the signature to the body, separated by the signature delimiter.
|
||||
*
|
||||
* @param {string} body - The body to append the signature to.
|
||||
* @param {string} signature - The signature to append.
|
||||
* @returns {string} - The body with the signature appended.
|
||||
*/
|
||||
export function appendSignature(body, signature) {
|
||||
const cleanedSignature = cleanSignature(signature);
|
||||
// if signature is already present, return body
|
||||
if (findSignatureInBody(body, cleanedSignature) > -1) {
|
||||
return body;
|
||||
}
|
||||
|
||||
return `${body.trimEnd()}\n\n${appendDelimiter(cleanedSignature)}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the signature from the body, along with the signature delimiter.
|
||||
*
|
||||
* @param {string} body - The body to remove the signature from.
|
||||
* @param {string} signature - The signature to remove.
|
||||
* @returns {string} - The body with the signature removed.
|
||||
*/
|
||||
export function removeSignature(body, signature) {
|
||||
// this will find the index of the signature if it exists
|
||||
// Regardless of extra spaces or new lines after the signature, the index will be the same if present
|
||||
const cleanedSignature = cleanSignature(signature);
|
||||
const signatureIndex = findSignatureInBody(body, cleanedSignature);
|
||||
|
||||
// no need to trim the ends here, because it will simply be removed in the next method
|
||||
let newBody = body;
|
||||
|
||||
// if signature is present, remove it and trim it
|
||||
// trimming will ensure any spaces or new lines before the signature are removed
|
||||
// This means we will have the delimiter at the end
|
||||
if (signatureIndex > -1) {
|
||||
newBody = newBody.substring(0, signatureIndex).trimEnd();
|
||||
}
|
||||
|
||||
// let's find the delimiter and remove it
|
||||
const delimiterIndex = newBody.lastIndexOf(SIGNATURE_DELIMITER);
|
||||
if (
|
||||
delimiterIndex !== -1 &&
|
||||
delimiterIndex === newBody.length - SIGNATURE_DELIMITER.length // this will ensure the delimiter is at the end
|
||||
) {
|
||||
// if the delimiter is at the end, remove it
|
||||
newBody = newBody.substring(0, delimiterIndex);
|
||||
}
|
||||
|
||||
// return the value
|
||||
return newBody;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the old signature with the new signature.
|
||||
* If the old signature is not present, it will append the new signature.
|
||||
*
|
||||
* @param {string} body - The body to replace the signature in.
|
||||
* @param {string} oldSignature - The signature to replace.
|
||||
* @param {string} newSignature - The signature to replace the old signature with.
|
||||
* @returns {string} - The body with the old signature replaced with the new signature.
|
||||
*
|
||||
*/
|
||||
export function replaceSignature(body, oldSignature, newSignature) {
|
||||
const withoutSignature = removeSignature(body, oldSignature);
|
||||
return appendSignature(withoutSignature, newSignature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract text from markdown, and remove all images, code blocks, links, headers, bold, italic, lists etc.
|
||||
* Links will be converted to text, and not removed.
|
||||
*
|
||||
* @param {string} markdown - markdown text to be extracted
|
||||
* @returns
|
||||
*/
|
||||
export function extractTextFromMarkdown(markdown) {
|
||||
return markdown
|
||||
.replace(/```[\s\S]*?```/g, '') // Remove code blocks
|
||||
.replace(/`.*?`/g, '') // Remove inline code
|
||||
.replace(/!\[.*?\]\(.*?\)/g, '') // Remove images before removing links
|
||||
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Remove links but keep the text
|
||||
.replace(/#+\s*|[*_-]{1,3}/g, '') // Remove headers, bold, italic, lists etc.
|
||||
.split('\n')
|
||||
.map(line => line.trim())
|
||||
.filter(Boolean)
|
||||
.join('\n') // Trim each line & remove any lines only having spaces
|
||||
.replace(/\n{2,}/g, '\n') // Remove multiple consecutive newlines (blank lines)
|
||||
.trim(); // Trim any extra space
|
||||
}
|
||||
@@ -1,21 +1,34 @@
|
||||
const setArrayValues = item => {
|
||||
return item.values[0]?.id ? item.values.map(val => val.id) : item.values;
|
||||
};
|
||||
|
||||
const generateValues = item => {
|
||||
if (item.attribute_key === 'content') {
|
||||
const values = item.values || '';
|
||||
return values.split(',');
|
||||
}
|
||||
if (Array.isArray(item.values)) {
|
||||
return setArrayValues(item);
|
||||
}
|
||||
if (typeof item.values === 'object') {
|
||||
return [item.values.id];
|
||||
}
|
||||
if (!item.values) {
|
||||
return [];
|
||||
}
|
||||
return [item.values];
|
||||
};
|
||||
|
||||
const generatePayload = data => {
|
||||
// Make a copy of data to avoid vue data reactivity issues
|
||||
const filters = JSON.parse(JSON.stringify(data));
|
||||
let payload = filters.map(item => {
|
||||
if (Array.isArray(item.values)) {
|
||||
item.values = setArrayValues(item);
|
||||
} else if (typeof item.values === 'object') {
|
||||
item.values = [item.values.id];
|
||||
} else if (!item.values) {
|
||||
item.values = [];
|
||||
} else {
|
||||
item.values = [item.values];
|
||||
}
|
||||
// If item key is content, we will split it using comma and return as array
|
||||
// FIX ME: Make this generic option instead of using the key directly here
|
||||
item.values = generateValues(item);
|
||||
return item;
|
||||
});
|
||||
|
||||
// For every query added, the query_operator is set default to and so the
|
||||
// last query will have an extra query_operator, this would break the api.
|
||||
// Setting this to null for all query payload
|
||||
|
||||
@@ -0,0 +1,171 @@
|
||||
import {
|
||||
findSignatureInBody,
|
||||
appendSignature,
|
||||
removeSignature,
|
||||
replaceSignature,
|
||||
cleanSignature,
|
||||
extractTextFromMarkdown,
|
||||
} from '../editorHelper';
|
||||
|
||||
const NEW_SIGNATURE = 'This is a new signature';
|
||||
|
||||
const DOES_NOT_HAVE_SIGNATURE = {
|
||||
'no signature': {
|
||||
body: 'This is a test',
|
||||
signature: 'This is a signature',
|
||||
},
|
||||
'text after signature': {
|
||||
body: 'This is a test\n\n--\n\nThis is a signature\n\nThis is more text',
|
||||
signature: 'This is a signature',
|
||||
},
|
||||
'signature has images': {
|
||||
body: 'This is a test',
|
||||
signature:
|
||||
'Testing\n',
|
||||
},
|
||||
'signature has non commonmark syntax': {
|
||||
body: 'This is a test',
|
||||
signature: '- Signature',
|
||||
},
|
||||
'signature has trailing spaces': {
|
||||
body: 'This is a test',
|
||||
signature: '**hello** \n**world**',
|
||||
},
|
||||
};
|
||||
|
||||
const HAS_SIGNATURE = {
|
||||
'signature at end': {
|
||||
body: 'This is a test\n\n--\n\nThis is a signature',
|
||||
signature: 'This is a signature',
|
||||
},
|
||||
'signature at end with spaces and new lines': {
|
||||
body: 'This is a test\n\n--\n\nThis is a signature \n\n',
|
||||
signature: 'This is a signature ',
|
||||
},
|
||||
'no text before signature': {
|
||||
body: '\n\n--\n\nThis is a signature',
|
||||
signature: 'This is a signature',
|
||||
},
|
||||
'signature has non-commonmark syntax': {
|
||||
body: '\n\n--\n\n* Signature',
|
||||
signature: '- Signature',
|
||||
},
|
||||
};
|
||||
|
||||
describe('findSignatureInBody', () => {
|
||||
it('returns -1 if there is no signature', () => {
|
||||
Object.keys(DOES_NOT_HAVE_SIGNATURE).forEach(key => {
|
||||
const { body, signature } = DOES_NOT_HAVE_SIGNATURE[key];
|
||||
expect(findSignatureInBody(body, signature)).toBe(-1);
|
||||
});
|
||||
});
|
||||
it('returns the index of the signature if there is one', () => {
|
||||
Object.keys(HAS_SIGNATURE).forEach(key => {
|
||||
const { body, signature } = HAS_SIGNATURE[key];
|
||||
expect(findSignatureInBody(body, signature)).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('appendSignature', () => {
|
||||
it('appends the signature if it is not present', () => {
|
||||
Object.keys(DOES_NOT_HAVE_SIGNATURE).forEach(key => {
|
||||
const { body, signature } = DOES_NOT_HAVE_SIGNATURE[key];
|
||||
const cleanedSignature = cleanSignature(signature);
|
||||
expect(
|
||||
appendSignature(body, signature).includes(cleanedSignature)
|
||||
).toBeTruthy();
|
||||
});
|
||||
});
|
||||
it('does not append signature if already present', () => {
|
||||
Object.keys(HAS_SIGNATURE).forEach(key => {
|
||||
const { body, signature } = HAS_SIGNATURE[key];
|
||||
expect(appendSignature(body, signature)).toBe(body);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('removeSignature', () => {
|
||||
it('does not remove signature if not present', () => {
|
||||
Object.keys(DOES_NOT_HAVE_SIGNATURE).forEach(key => {
|
||||
const { body, signature } = DOES_NOT_HAVE_SIGNATURE[key];
|
||||
expect(removeSignature(body, signature)).toBe(body);
|
||||
});
|
||||
});
|
||||
it('removes signature if present at the end', () => {
|
||||
const { body, signature } = HAS_SIGNATURE['signature at end'];
|
||||
expect(removeSignature(body, signature)).toBe('This is a test\n\n');
|
||||
});
|
||||
it('removes signature if present with spaces and new lines', () => {
|
||||
const { body, signature } = HAS_SIGNATURE[
|
||||
'signature at end with spaces and new lines'
|
||||
];
|
||||
expect(removeSignature(body, signature)).toBe('This is a test\n\n');
|
||||
});
|
||||
it('removes signature if present without text before it', () => {
|
||||
const { body, signature } = HAS_SIGNATURE['no text before signature'];
|
||||
expect(removeSignature(body, signature)).toBe('\n\n');
|
||||
});
|
||||
it('removes just the delimiter if no signature is present', () => {
|
||||
expect(removeSignature('This is a test\n\n--', 'This is a signature')).toBe(
|
||||
'This is a test\n\n'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('replaceSignature', () => {
|
||||
it('appends the new signature if not present', () => {
|
||||
Object.keys(DOES_NOT_HAVE_SIGNATURE).forEach(key => {
|
||||
const { body, signature } = DOES_NOT_HAVE_SIGNATURE[key];
|
||||
expect(replaceSignature(body, signature, NEW_SIGNATURE)).toBe(
|
||||
`${body}\n\n--\n\n${NEW_SIGNATURE}`
|
||||
);
|
||||
});
|
||||
});
|
||||
it('removes signature if present at the end', () => {
|
||||
const { body, signature } = HAS_SIGNATURE['signature at end'];
|
||||
expect(replaceSignature(body, signature, NEW_SIGNATURE)).toBe(
|
||||
`This is a test\n\n--\n\n${NEW_SIGNATURE}`
|
||||
);
|
||||
});
|
||||
it('removes signature if present with spaces and new lines', () => {
|
||||
const { body, signature } = HAS_SIGNATURE[
|
||||
'signature at end with spaces and new lines'
|
||||
];
|
||||
expect(replaceSignature(body, signature, NEW_SIGNATURE)).toBe(
|
||||
`This is a test\n\n--\n\n${NEW_SIGNATURE}`
|
||||
);
|
||||
});
|
||||
it('removes signature if present without text before it', () => {
|
||||
const { body, signature } = HAS_SIGNATURE['no text before signature'];
|
||||
expect(replaceSignature(body, signature, NEW_SIGNATURE)).toBe(
|
||||
`\n\n--\n\n${NEW_SIGNATURE}`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('extractTextFromMarkdown', () => {
|
||||
it('should extract text from markdown and remove all images, code blocks, links, headers, bold, italic, lists etc.', () => {
|
||||
const markdown = `
|
||||
# Hello World
|
||||
|
||||
This is a **bold** text with a [link](https://example.com).
|
||||
|
||||
\`\`\`javascript
|
||||
const foo = 'bar';
|
||||
console.log(foo);
|
||||
\`\`\`
|
||||
|
||||
Here's an image: 
|
||||
|
||||
- List item 1
|
||||
- List item 2
|
||||
|
||||
*Italic text*
|
||||
`;
|
||||
|
||||
const expected =
|
||||
"Hello World\nThis is a bold text with a link.\nHere's an image:\nList item 1\nList item 2\nItalic text";
|
||||
expect(extractTextFromMarkdown(markdown)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
@@ -25,10 +25,10 @@ describe('#Upload Helpers', () => {
|
||||
|
||||
axios.post.mockResolvedValueOnce(mockResponse);
|
||||
|
||||
const result = await uploadFile(mockFile);
|
||||
const result = await uploadFile(mockFile, '1602');
|
||||
|
||||
expect(axios.post).toHaveBeenCalledWith(
|
||||
'/api/v1/upload',
|
||||
'/api/v1/accounts/1602/upload',
|
||||
expect.any(FormData),
|
||||
{ headers: { 'Content-Type': 'multipart/form-data' } }
|
||||
);
|
||||
|
||||
@@ -21,17 +21,25 @@ const HEADERS = {
|
||||
* @param {File} file - The file to be uploaded. It should be a File object (typically coming from a file input element).
|
||||
* @returns {Promise} A promise that resolves with the server's response when the upload is successful, or rejects if there's an error.
|
||||
*/
|
||||
export async function uploadFile(file) {
|
||||
export async function uploadFile(file, accountId) {
|
||||
// Create a new FormData instance.
|
||||
let formData = new FormData();
|
||||
|
||||
if (!accountId) {
|
||||
accountId = window.location.pathname.split('/')[3];
|
||||
}
|
||||
|
||||
// Append the file to the FormData instance under the key 'attachment'.
|
||||
formData.append('attachment', file);
|
||||
|
||||
// Use axios to send a POST request to the upload endpoint.
|
||||
const { data } = await axios.post(`/api/${API_VERSION}/upload`, formData, {
|
||||
headers: HEADERS,
|
||||
});
|
||||
const { data } = await axios.post(
|
||||
`/api/${API_VERSION}/accounts/${accountId}/upload`,
|
||||
formData,
|
||||
{
|
||||
headers: HEADERS,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
fileUrl: data.file_url,
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "Authenticating you with Facebook...",
|
||||
"ERROR_FB_AUTH": "Something went wrong, Please refresh page...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "Creating your Inbox...",
|
||||
"TITLE": "Configure Inbox Details",
|
||||
"DESC": ""
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "Personal message signature",
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"NOTE": "Create a unique message signature to appear at the end of every message you send from any inbox. You can also include an inline image, which is supported in live-chat, email, and API inboxes.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "جار المصادقة والربط مع الفيسبوك...",
|
||||
"ERROR_FB_AUTH": "حدث خطأ ما، الرجاء تحديث الصفحة...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "جار إنشاء قناة التواصل...",
|
||||
"TITLE": "تهيئة إعدادات قناة التواصل",
|
||||
"DESC": ""
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "توقيع الرسالة الشخصية",
|
||||
"NOTE": "إنشاء توقيع رسالة شخصية يتم إضافتها إلى جميع الرسائل التي ترسلها من المنصة. استخدم محرر المحتوى الغني لإنشاء توقيع شديد التخصيص.",
|
||||
"NOTE": "Create a unique message signature to appear at the end of every message you send from any inbox. You can also include an inline image, which is supported in live-chat, email, and API inboxes.",
|
||||
"BTN_TEXT": "حفظ توقيع الرسالة",
|
||||
"API_ERROR": "تعذر إرسال الرسالة! حاول مرة أخرى",
|
||||
"API_SUCCESS": "تم حفظ التوقيع بنجاح",
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "Authenticating you with Facebook...",
|
||||
"ERROR_FB_AUTH": "Something went wrong, Please refresh page...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "Creating your Inbox...",
|
||||
"TITLE": "Configure Inbox Details",
|
||||
"DESC": ""
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "Personal message signature",
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"NOTE": "Create a unique message signature to appear at the end of every message you send from any inbox. You can also include an inline image, which is supported in live-chat, email, and API inboxes.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "S'està autenticant amb Facebook...",
|
||||
"ERROR_FB_AUTH": "Alguna cosa ha anat malament, actualitza la pàgina...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "S'està creant la safata d'entrada...",
|
||||
"TITLE": "Configura els detalls de la safata d'entrada",
|
||||
"DESC": ""
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "Personal message signature",
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"NOTE": "Create a unique message signature to appear at the end of every message you send from any inbox. You can also include an inline image, which is supported in live-chat, email, and API inboxes.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "Ověřování pomocí Facebooku...",
|
||||
"ERROR_FB_AUTH": "Něco se pokazilo, prosím obnovte stránku...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "Vytvářím vaši doručenou poštu...",
|
||||
"TITLE": "Konfigurace detailů doručené pošty",
|
||||
"DESC": ""
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "Personal message signature",
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"NOTE": "Create a unique message signature to appear at the end of every message you send from any inbox. You can also include an inline image, which is supported in live-chat, email, and API inboxes.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "Autentificerer dig med Facebook...",
|
||||
"ERROR_FB_AUTH": "Noget gik galt, Opdatér siden...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "Opretter din indbakke...",
|
||||
"TITLE": "Indstil Indbakkedetaljer",
|
||||
"DESC": ""
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "Personlig beskedsignatur",
|
||||
"NOTE": "Opret en personlig besked signatur, der vil blive føjet til alle de meddelelser, du sender fra din e-mail indbakke. Brug den rige content editor til at oprette en meget personlig signatur.",
|
||||
"NOTE": "Create a unique message signature to appear at the end of every message you send from any inbox. You can also include an inline image, which is supported in live-chat, email, and API inboxes.",
|
||||
"BTN_TEXT": "Gem beskedsignatur",
|
||||
"API_ERROR": "Kunne ikke gemme signatur! Prøv igen",
|
||||
"API_SUCCESS": "Signatur gemt",
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "Authentifizierung mit Facebook ...",
|
||||
"ERROR_FB_AUTH": "Es ist ein Fehler aufgetreten. Bitte Seite aktualisieren ...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "Erstellen Sie Ihren Posteingang ...",
|
||||
"TITLE": "Posteingangsdetails konfigurieren",
|
||||
"DESC": ""
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "Persönliche Nachrichtensignatur",
|
||||
"NOTE": "Erstellen Sie eine persönliche Nachrichtensignatur, die allen Nachrichten hinzugefügt wird, die Sie aus Ihrem E-Mail-Posteingang senden. Verwenden Sie den Rich-Content-Editor, um eine stark personalisierte Signatur zu erstellen.",
|
||||
"NOTE": "Create a unique message signature to appear at the end of every message you send from any inbox. You can also include an inline image, which is supported in live-chat, email, and API inboxes.",
|
||||
"BTN_TEXT": "Nachrichten-Signatur speichern",
|
||||
"API_ERROR": "Signatur konnte nicht gespeichert werden! Versuch es noch einmal",
|
||||
"API_SUCCESS": "Signatur erfolgreich gespeichert",
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "Πιστοποίηση ταυτότητας στο Facebook...",
|
||||
"ERROR_FB_AUTH": "Κάτι πήγε στραβά, Παρακαλώ ανανεώστε την σελίδα...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "Δημιουργία κιβωτίου εισερχομένων...",
|
||||
"TITLE": "Διαμόρφωση λεπτομερειών κιβωτίου",
|
||||
"DESC": ""
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "Προσωπική υπογραφή μηνύματος",
|
||||
"NOTE": "Δημιουργήστε μια προσωπική υπογραφή, η οποία θα προστεθεί σε όλα τα μηνύματα που στέλνετε από τα εισερχόμενα email σας. Χρησιμοποιήστε τον επεξεργαστή πλούσιου περιεχομένου για να δημιουργήσετε μια εξατομικευμένη υπογραφή.",
|
||||
"NOTE": "Create a unique message signature to appear at the end of every message you send from any inbox. You can also include an inline image, which is supported in live-chat, email, and API inboxes.",
|
||||
"BTN_TEXT": "Αποθήκευση υπογραφής μηνύματος",
|
||||
"API_ERROR": "Δεν ήταν δυνατή η αποθήκευση της υπογραφής! Δοκιμάστε ξανά",
|
||||
"API_SUCCESS": "Η υπογραφή αποθηκεύτηκε με επιτυχία",
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "Authenticating you with Facebook...",
|
||||
"ERROR_FB_AUTH": "Something went wrong, Please refresh page...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "Creating your Inbox...",
|
||||
"TITLE": "Configure Inbox Details",
|
||||
"DESC": ""
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "Personal message signature",
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"NOTE": "Create a unique message signature to appear at the end of every message you send from any inbox. You can also include an inline image, which is supported in live-chat, email, and API inboxes.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "Autenticándote con Facebook...",
|
||||
"ERROR_FB_AUTH": "Algo salió mal, Por favor actualiza la página...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "Creando tu bandeja de entrada...",
|
||||
"TITLE": "Configurar detalles de la Bandeja de Entrada",
|
||||
"DESC": ""
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "Firma de mensaje personal",
|
||||
"NOTE": "Cree una firma de mensaje personal que se añadirá a todos los mensajes que envíe desde su buzón de correo electrónico. Utilice el rico editor de contenidos para crear una firma altamente personalizada.",
|
||||
"NOTE": "Crea una firma de mensaje única para que aparezca al final de cada mensaje que envíes desde cualquier bandeja de entrada. También puede incluir una imagen en línea, que es soportada en las bandejas de entrada de live-chat, correo electrónico y API.",
|
||||
"BTN_TEXT": "Guardar firma de mensaje",
|
||||
"API_ERROR": "¡No se pudo guardar la firma! Inténtalo de nuevo",
|
||||
"API_SUCCESS": "Firma guardada correctamente",
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "در حال احراز هویت با فیس بوک...",
|
||||
"ERROR_FB_AUTH": "اشکالی پیش آمد.. لطفا دوباره سعی کنید...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "در حال ساخت صندوق ورودی...",
|
||||
"TITLE": "تنظیمات صفحه ورودی",
|
||||
"DESC": ""
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "امضای پیام شخصی",
|
||||
"NOTE": "یک امضای پیام شخصی ایجاد کنید که به همه پیامهایی که از صندوق ورودی ایمیل ارسال میکنید اضافه شود. از ویرایشگر محتوای غنی برای ایجاد یک امضای بسیار شخصی استفاده کنید.",
|
||||
"NOTE": "Create a unique message signature to appear at the end of every message you send from any inbox. You can also include an inline image, which is supported in live-chat, email, and API inboxes.",
|
||||
"BTN_TEXT": "ذخیره امضای پیام",
|
||||
"API_ERROR": "امضا ذخیره نشد! دوباره امتحان کنید",
|
||||
"API_SUCCESS": "امضا با موفقیت ذخیره شد",
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "Varmennetaan sinua Facebookissa...",
|
||||
"ERROR_FB_AUTH": "Jokin meni pieleen, päivitä sivu...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "Luodaan postilaatikkoasi...",
|
||||
"TITLE": "Määritä postilaatikon tiedot",
|
||||
"DESC": ""
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "Personal message signature",
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"NOTE": "Create a unique message signature to appear at the end of every message you send from any inbox. You can also include an inline image, which is supported in live-chat, email, and API inboxes.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "Authentification avec Facebook ...",
|
||||
"ERROR_FB_AUTH": "Une erreur s'est produite, veuillez rafraîchir la page ...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "Création de votre boîte de réception ...",
|
||||
"TITLE": "Configurer les détails de la boîte de réception",
|
||||
"DESC": ""
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "Signature du message personnel",
|
||||
"NOTE": "Créez une signature de message personnelle qui sera ajoutée à tous les messages que vous envoyez depuis votre boîte aux lettres électronique. Utilisez l'éditeur de contenu riche pour créer une signature personnalisée.",
|
||||
"NOTE": "Create a unique message signature to appear at the end of every message you send from any inbox. You can also include an inline image, which is supported in live-chat, email, and API inboxes.",
|
||||
"BTN_TEXT": "Enregistrer la signature du message",
|
||||
"API_ERROR": "Impossible d'enregistrer la signature ! Réessayez",
|
||||
"API_SUCCESS": "Signature enregistrée avec succès",
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "מאמת אותך עם פייסבוק...",
|
||||
"ERROR_FB_AUTH": "משהו השתבש, אנא רענן את הדף...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "יוצר את תיבת הדואר הנכנס שלך...",
|
||||
"TITLE": "הגדר את פרטי תיבת הדואר הנכנס",
|
||||
"DESC": ""
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "חתימת הודעה אישית",
|
||||
"NOTE": "ניתן לרשום חתימה לכל ההודעות שתרשום ללקוחות שלך.",
|
||||
"NOTE": "Create a unique message signature to appear at the end of every message you send from any inbox. You can also include an inline image, which is supported in live-chat, email, and API inboxes.",
|
||||
"BTN_TEXT": "שמירה",
|
||||
"API_ERROR": "לא ניתן לשמור את החתימה! אנא נסה שנית",
|
||||
"API_SUCCESS": "החתימה נשמרה בהצלחה",
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "Authenticating you with Facebook...",
|
||||
"ERROR_FB_AUTH": "Something went wrong, Please refresh page...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "Creating your Inbox...",
|
||||
"TITLE": "Configure Inbox Details",
|
||||
"DESC": ""
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "Personal message signature",
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"NOTE": "Create a unique message signature to appear at the end of every message you send from any inbox. You can also include an inline image, which is supported in live-chat, email, and API inboxes.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "Authenticating you with Facebook...",
|
||||
"ERROR_FB_AUTH": "Something went wrong, Please refresh page...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "Creating your Inbox...",
|
||||
"TITLE": "Configure Inbox Details",
|
||||
"DESC": ""
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "Personal message signature",
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"NOTE": "Create a unique message signature to appear at the end of every message you send from any inbox. You can also include an inline image, which is supported in live-chat, email, and API inboxes.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "Facebookkal azonosítunk...",
|
||||
"ERROR_FB_AUTH": "Valami elromlott, kérjük töltsd újra az oldalt...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "Inbox létrehozása...",
|
||||
"TITLE": "Inbox részletek beállítása",
|
||||
"DESC": ""
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "Személyes üzenet aláírás",
|
||||
"NOTE": "Hozz létre egy személyes üzenetaláírást, amelyet az e-mail postafiókodból küldött összes üzenethez hozzáadhatsz. A sokszínű tartalomszerkesztő segítségével személyre szabott aláírást hozhatsz létre.",
|
||||
"NOTE": "Create a unique message signature to appear at the end of every message you send from any inbox. You can also include an inline image, which is supported in live-chat, email, and API inboxes.",
|
||||
"BTN_TEXT": "Üzenet aláírás mentése",
|
||||
"API_ERROR": "Nem sikerült menteni az aláírást! Próbáld újra",
|
||||
"API_SUCCESS": "Aláírás sikeresen mentve",
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "Authenticating you with Facebook...",
|
||||
"ERROR_FB_AUTH": "Something went wrong, Please refresh page...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "Creating your Inbox...",
|
||||
"TITLE": "Configure Inbox Details",
|
||||
"DESC": ""
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"MESSAGE_SIGNATURE_SECTION": {
|
||||
"TITLE": "Personal message signature",
|
||||
"NOTE": "Create a personal message signature that would be added to all the messages you send from your email inbox. Use the rich content editor to create a highly personalised signature.",
|
||||
"NOTE": "Create a unique message signature to appear at the end of every message you send from any inbox. You can also include an inline image, which is supported in live-chat, email, and API inboxes.",
|
||||
"BTN_TEXT": "Save message signature",
|
||||
"API_ERROR": "Couldn't save signature! Try again",
|
||||
"API_SUCCESS": "Signature saved successfully",
|
||||
|
||||
@@ -371,6 +371,8 @@
|
||||
"DETAILS": {
|
||||
"LOADING_FB": "Mengautentikasi Anda dengan Facebook...",
|
||||
"ERROR_FB_AUTH": "Ada yang tidak beres, Harap refresh halaman...",
|
||||
"ERROR_FB_UNAUTHORIZED": "You're not authorized to perform this action. ",
|
||||
"ERROR_FB_UNAUTHORIZED_HELP": "Please ensure you have access to the Facebook page with full control. You can read more about Facebook roles <a href=\" https://www.facebook.com/help/187316341316631\">here</a>.",
|
||||
"CREATING_CHANNEL": "Membuat kotak masuk Anda...",
|
||||
"TITLE": "Konfigurasi Detail Kotak Masuk",
|
||||
"DESC": ""
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user