chore: RTL configuration (#6521)
* chore: RTL configuration * Adds scss file
This commit is contained in:
@@ -1,5 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="!authUIFlags.isFetching" id="app" class="app-wrapper app-root">
|
<div
|
||||||
|
v-if="!authUIFlags.isFetching"
|
||||||
|
id="app"
|
||||||
|
class="app-wrapper app-root"
|
||||||
|
:class="{ 'app-rtl--wrapper': isRTLView }"
|
||||||
|
>
|
||||||
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
|
<update-banner :latest-chatwoot-version="latestChatwootVersion" />
|
||||||
<transition name="fade" mode="out-in">
|
<transition name="fade" mode="out-in">
|
||||||
<router-view />
|
<router-view />
|
||||||
@@ -22,6 +27,7 @@ import NetworkNotification from './components/NetworkNotification';
|
|||||||
import UpdateBanner from './components/app/UpdateBanner.vue';
|
import UpdateBanner from './components/app/UpdateBanner.vue';
|
||||||
import vueActionCable from './helper/actionCable';
|
import vueActionCable from './helper/actionCable';
|
||||||
import WootSnackbarBox from './components/SnackbarContainer';
|
import WootSnackbarBox from './components/SnackbarContainer';
|
||||||
|
import rtlMixin from 'shared/mixins/rtlMixin';
|
||||||
import {
|
import {
|
||||||
registerSubscription,
|
registerSubscription,
|
||||||
verifyServiceWorkerExistence,
|
verifyServiceWorkerExistence,
|
||||||
@@ -38,6 +44,8 @@ export default {
|
|||||||
WootSnackbarBox,
|
WootSnackbarBox,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
mixins: [rtlMixin],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showAddAccountModal: false,
|
showAddAccountModal: false,
|
||||||
|
|||||||
436
app/javascript/dashboard/assets/scss/_rtl.scss
Normal file
436
app/javascript/dashboard/assets/scss/_rtl.scss
Normal file
@@ -0,0 +1,436 @@
|
|||||||
|
.app-rtl--wrapper {
|
||||||
|
direction: rtl;
|
||||||
|
|
||||||
|
// Primary sidebar
|
||||||
|
.primary--sidebar {
|
||||||
|
border-left: 1px solid var(--s-50);
|
||||||
|
border-right: 0;
|
||||||
|
|
||||||
|
.options-menu.dropdown-pane {
|
||||||
|
right: var(--space-smaller);
|
||||||
|
|
||||||
|
.auto-offline--toggle {
|
||||||
|
padding: var(--space-smaller) var(--space-one) var(--space-smaller)
|
||||||
|
var(--space-smaller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secondary sidebar
|
||||||
|
.secondary-sidebar {
|
||||||
|
.secondary-menu {
|
||||||
|
border-left: 1px solid var(--s-50);
|
||||||
|
border-right: 0;
|
||||||
|
|
||||||
|
.nested.vertical.menu {
|
||||||
|
align-items: flex-start;
|
||||||
|
|
||||||
|
li {
|
||||||
|
direction: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.secondary-menu--icon {
|
||||||
|
margin-left: var(--space-smaller);
|
||||||
|
margin-right: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-context--group .account-context--switch-group {
|
||||||
|
--overlay-shadow: linear-gradient(
|
||||||
|
to left,
|
||||||
|
rgba(255, 255, 255, 0) 0%,
|
||||||
|
rgba(255, 255, 255, 1) 50%
|
||||||
|
);
|
||||||
|
background-image: var(--overlay-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Help center sidebar
|
||||||
|
.sidebar-header--wrap .header-title--wrap {
|
||||||
|
margin-left: unset;
|
||||||
|
margin-right: var(--space-small);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Woot button
|
||||||
|
.button {
|
||||||
|
.icon--emoji + .button__content {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: var(--space-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon--font + .button__content {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: var(--space-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon + .button__content {
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: var(--space-small);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings header
|
||||||
|
.settings-header {
|
||||||
|
.header--icon {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: $space-small;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings.back-button {
|
||||||
|
direction: initial;
|
||||||
|
margin-left: var(--space-normal);
|
||||||
|
margin-right: var(--space-smaller);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Settings header action button
|
||||||
|
.button--fixed-top {
|
||||||
|
left: $space-small;
|
||||||
|
position: fixed;
|
||||||
|
right: unset;
|
||||||
|
top: $space-small;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Woot Tabs
|
||||||
|
.tabs-title {
|
||||||
|
&:first-child {
|
||||||
|
margin-left: $space-slab;
|
||||||
|
margin-right: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-left: unset;
|
||||||
|
margin-right: $space-slab;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// woot tables
|
||||||
|
table,
|
||||||
|
thead,
|
||||||
|
th {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Table footer
|
||||||
|
.footer {
|
||||||
|
.page-meta {
|
||||||
|
direction: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wizard box
|
||||||
|
.wizard-box {
|
||||||
|
direction: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversation details
|
||||||
|
.conversation-details-wrap {
|
||||||
|
.conv-header {
|
||||||
|
.user {
|
||||||
|
margin-left: var(--space-normal);
|
||||||
|
margin-right: unset;
|
||||||
|
|
||||||
|
.user--profile__meta {
|
||||||
|
margin-left: unset;
|
||||||
|
margin-right: $space-slab;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions--container .resolve-actions {
|
||||||
|
margin-left: unset;
|
||||||
|
margin-right: var(--space-small);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.conversation-panel {
|
||||||
|
// Message text
|
||||||
|
.text-content {
|
||||||
|
p {
|
||||||
|
unicode-bidi: plaintext;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
padding-left: unset;
|
||||||
|
padding-right: var(--space-two);
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Message items and actions
|
||||||
|
li {
|
||||||
|
&.right {
|
||||||
|
.sender--info {
|
||||||
|
padding: var(--space-small) var(--space-smaller)
|
||||||
|
var(--space-smaller) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.context-menu-wrap {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversation footer
|
||||||
|
.conversation-footer {
|
||||||
|
.preview-item {
|
||||||
|
direction: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom attributes section in conversation sidebar
|
||||||
|
.conversation-sidebar-wrap .checkbox-wrap {
|
||||||
|
.checkbox {
|
||||||
|
margin-left: var(--space-small);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversation sidebar toggle button
|
||||||
|
.sidebar-toggle--button {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversation sidebar close button
|
||||||
|
.close-button--rtl {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve actions button
|
||||||
|
.resolve-actions {
|
||||||
|
.button-group .button:first-child {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: var(--border-radius-normal);
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: var(--border-radius-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group .button:last-child {
|
||||||
|
border-bottom-left-radius: var(--border-radius-normal);
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-top-left-radius: var(--border-radius-normal);
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conversation list
|
||||||
|
.conversations-list-wrap {
|
||||||
|
border-right: 0;
|
||||||
|
|
||||||
|
.conversation {
|
||||||
|
.conversation--meta {
|
||||||
|
left: $space-normal;
|
||||||
|
right: unset;
|
||||||
|
|
||||||
|
.unread {
|
||||||
|
margin-left: unset;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.show-more--button {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-header--wrap .search--input {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secondary sidebar toggle button
|
||||||
|
.toggle-sidebar {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: var(--space-minus-small);
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bulk actions
|
||||||
|
.bulk-action__container {
|
||||||
|
.triangle {
|
||||||
|
left: var(--triangle-position);
|
||||||
|
right: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-action__agents {
|
||||||
|
left: var(--space-small);
|
||||||
|
right: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.labels-container {
|
||||||
|
left: var(--space-small);
|
||||||
|
right: unset;
|
||||||
|
|
||||||
|
.label-checkbox {
|
||||||
|
margin: 0 0 0 var(--space-one);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.actions-container {
|
||||||
|
left: var(--space-small);
|
||||||
|
right: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bulk-action__teams {
|
||||||
|
left: var(--space-small);
|
||||||
|
right: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contact notes
|
||||||
|
.card.note-wrap {
|
||||||
|
.time-stamp {
|
||||||
|
unicode-bidi: plaintext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Labels
|
||||||
|
.label {
|
||||||
|
direction: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notification panel
|
||||||
|
.notification-wrap {
|
||||||
|
left: 0;
|
||||||
|
right: var(--space-jumbo);
|
||||||
|
|
||||||
|
.action-button {
|
||||||
|
margin-left: var(--space-small);
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.notification-content--wrap {
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: var(--space-slab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Help center
|
||||||
|
.article-container .row--article-block {
|
||||||
|
td:last-child {
|
||||||
|
direction: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-left-wrap .page-title {
|
||||||
|
margin-right: var(--space-slab) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.portal-container .container {
|
||||||
|
margin-left: unset !important;
|
||||||
|
margin-right: var(--space-small);
|
||||||
|
|
||||||
|
.configuration-items--wrap {
|
||||||
|
margin-left: var(--space-mega);
|
||||||
|
margin-right: unset !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead th {
|
||||||
|
padding-left: var(--space-one);
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody td {
|
||||||
|
padding-left: var(--space-one);
|
||||||
|
padding-right: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.portal-popover__container .portal {
|
||||||
|
.actions-container {
|
||||||
|
margin-left: unset;
|
||||||
|
margin-right: var(--space-one);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.edit-article--container {
|
||||||
|
.header-right--wrap {
|
||||||
|
.button-group .button:first-child {
|
||||||
|
border-bottom-left-radius: 0;
|
||||||
|
border-bottom-right-radius: var(--border-radius-normal);
|
||||||
|
border-top-left-radius: 0;
|
||||||
|
border-top-right-radius: var(--border-radius-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-group .button:last-child {
|
||||||
|
border-bottom-left-radius: var(--border-radius-normal);
|
||||||
|
border-bottom-right-radius: 0;
|
||||||
|
border-top-left-radius: var(--border-radius-normal);
|
||||||
|
border-top-right-radius: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-left--wrap {
|
||||||
|
.back-button {
|
||||||
|
direction: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article--buttons {
|
||||||
|
.dropdown-pane {
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
right: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-button {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-settings--container {
|
||||||
|
border-left: 0;
|
||||||
|
border-right: 1px solid var(--color-border-light);
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
margin-left: 0;
|
||||||
|
margin-right: var(--space-normal);
|
||||||
|
padding-left: 0;
|
||||||
|
padding-right: var(--space-normal);
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-list--container .header-left--wrap {
|
||||||
|
direction: initial;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widget builder
|
||||||
|
.widget-builder-container .widget-preview {
|
||||||
|
direction: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other changes
|
||||||
|
|
||||||
|
.account-selector--wrap {
|
||||||
|
direction: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inbox--name {
|
||||||
|
direction: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.colorpicker--chrome {
|
||||||
|
direction: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mention--box {
|
||||||
|
direction: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact--details .contact--bio {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.merge-contacts .child-contact-wrap {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact--form .input-group {
|
||||||
|
direction: initial;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,6 +41,7 @@
|
|||||||
@import 'layout';
|
@import 'layout';
|
||||||
@import 'animations';
|
@import 'animations';
|
||||||
@import 'foundation-custom';
|
@import 'foundation-custom';
|
||||||
|
@import 'rtl';
|
||||||
|
|
||||||
@import 'widgets/buttons';
|
@import 'widgets/buttons';
|
||||||
@import 'widgets/conv-header';
|
@import 'widgets/conv-header';
|
||||||
|
|||||||
@@ -739,4 +739,9 @@ export const getLanguageName = (languageCode = '') => {
|
|||||||
return languageObj.name || '';
|
return languageObj.name || '';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getLanguageDirection = (languageCode = '') => {
|
||||||
|
const rtlLanguageIds = ['ar', 'as', 'fa', 'he', 'ku', 'ur'];
|
||||||
|
return rtlLanguageIds.includes(languageCode);
|
||||||
|
};
|
||||||
|
|
||||||
export default languages;
|
export default languages;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { getLanguageName } from '../languages';
|
import { getLanguageName, getLanguageDirection } from '../languages';
|
||||||
|
|
||||||
describe('#getLanguageName', () => {
|
describe('#getLanguageName', () => {
|
||||||
it('Returns correct language name', () => {
|
it('Returns correct language name', () => {
|
||||||
@@ -8,3 +8,10 @@ describe('#getLanguageName', () => {
|
|||||||
expect(getLanguageName('')).toEqual('');
|
expect(getLanguageName('')).toEqual('');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#getLanguageDirection', () => {
|
||||||
|
it('Returns correct language direction', () => {
|
||||||
|
expect(getLanguageDirection('es')).toEqual(false);
|
||||||
|
expect(getLanguageDirection('ar')).toEqual(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -125,9 +125,11 @@ import configMixin from 'shared/mixins/configMixin';
|
|||||||
import accountMixin from '../../../../mixins/account';
|
import accountMixin from '../../../../mixins/account';
|
||||||
import { FEATURE_FLAGS } from '../../../../featureFlags';
|
import { FEATURE_FLAGS } from '../../../../featureFlags';
|
||||||
const semver = require('semver');
|
const semver = require('semver');
|
||||||
|
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||||
|
import { getLanguageDirection } from 'dashboard/components/widgets/conversation/advancedFilterItems/languages';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
mixins: [accountMixin, alertMixin, configMixin],
|
mixins: [accountMixin, alertMixin, configMixin, uiSettingsMixin],
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
id: '',
|
id: '',
|
||||||
@@ -249,11 +251,20 @@ export default {
|
|||||||
auto_resolve_duration: this.autoResolveDuration,
|
auto_resolve_duration: this.autoResolveDuration,
|
||||||
});
|
});
|
||||||
this.$root.$i18n.locale = this.locale;
|
this.$root.$i18n.locale = this.locale;
|
||||||
|
this.getAccount(this.id).locale = this.locale;
|
||||||
|
this.updateDirectionView(this.locale);
|
||||||
this.showAlert(this.$t('GENERAL_SETTINGS.UPDATE.SUCCESS'));
|
this.showAlert(this.$t('GENERAL_SETTINGS.UPDATE.SUCCESS'));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.showAlert(this.$t('GENERAL_SETTINGS.UPDATE.ERROR'));
|
this.showAlert(this.$t('GENERAL_SETTINGS.UPDATE.ERROR'));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateDirectionView(locale) {
|
||||||
|
const isRTLSupported = getLanguageDirection(locale);
|
||||||
|
this.updateUISettings({
|
||||||
|
rtl_view: isRTLSupported,
|
||||||
|
});
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
11
app/javascript/shared/mixins/rtlMixin.js
Normal file
11
app/javascript/shared/mixins/rtlMixin.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
mixins: [uiSettingsMixin],
|
||||||
|
computed: {
|
||||||
|
isRTLView() {
|
||||||
|
const { rtl_view: isRTLView } = this.uiSettings;
|
||||||
|
return isRTLView;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
29
app/javascript/shared/mixins/specs/rtlMixin.spec.js
Normal file
29
app/javascript/shared/mixins/specs/rtlMixin.spec.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import uiSettingsMixin from 'dashboard/mixins/uiSettings';
|
||||||
|
import { shallowMount } from '@vue/test-utils';
|
||||||
|
import rtlMixin from 'shared/mixins/rtlMixin';
|
||||||
|
|
||||||
|
describe('rtlMixin', () => {
|
||||||
|
it('returns is direction right-to-left view', () => {
|
||||||
|
const Component = {
|
||||||
|
render() {},
|
||||||
|
mixins: [rtlMixin, uiSettingsMixin],
|
||||||
|
data() {
|
||||||
|
return { uiSettings: { rtl_view: true } };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const wrapper = shallowMount(Component);
|
||||||
|
expect(wrapper.vm.isRTLView).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns is direction left-to-right view', () => {
|
||||||
|
const Component = {
|
||||||
|
render() {},
|
||||||
|
mixins: [rtlMixin, uiSettingsMixin],
|
||||||
|
data() {
|
||||||
|
return { uiSettings: { rtl_view: false } };
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const wrapper = shallowMount(Component);
|
||||||
|
expect(wrapper.vm.isRTLView).toBe(false);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user