mirror of
https://github.com/lingble/chatwoot.git
synced 2025-11-01 19:48:08 +00:00
feat: Vite + vue 3 💚 (#10047)
Fixes https://github.com/chatwoot/chatwoot/issues/8436 Fixes https://github.com/chatwoot/chatwoot/issues/9767 Fixes https://github.com/chatwoot/chatwoot/issues/10156 Fixes https://github.com/chatwoot/chatwoot/issues/6031 Fixes https://github.com/chatwoot/chatwoot/issues/5696 Fixes https://github.com/chatwoot/chatwoot/issues/9250 Fixes https://github.com/chatwoot/chatwoot/issues/9762 --------- Co-authored-by: Pranav <pranavrajs@gmail.com> Co-authored-by: Sivin Varghese <64252451+iamsivin@users.noreply.github.com>
This commit is contained in:
@@ -86,11 +86,9 @@ export default {
|
||||
return this.$t('UNREAD_VIEW.BOT');
|
||||
},
|
||||
avatarUrl() {
|
||||
// eslint-disable-next-line
|
||||
const BotImage = require('dashboard/assets/images/chatwoot_bot.png');
|
||||
const displayImage = this.useInboxAvatarForBot
|
||||
? this.inboxAvatarUrl
|
||||
: BotImage;
|
||||
: '/assets/images/chatwoot_bot.png';
|
||||
|
||||
if (this.message.message_type === MESSAGE_TYPE.TEMPLATE) {
|
||||
return displayImage;
|
||||
|
||||
@@ -122,7 +122,7 @@ export default {
|
||||
:title="message"
|
||||
:options="messageContentAttributes.items"
|
||||
:hide-fields="!!messageContentAttributes.submitted_values"
|
||||
@click="onOptionSelect"
|
||||
@option-select="onOptionSelect"
|
||||
/>
|
||||
</div>
|
||||
<ChatForm
|
||||
|
||||
@@ -18,10 +18,7 @@ export default {
|
||||
class="typing-bubble chat-bubble agent"
|
||||
:class="getThemeClass('bg-white', 'dark:bg-slate-700')"
|
||||
>
|
||||
<img
|
||||
src="~widget/assets/images/typing.gif"
|
||||
alt="Agent is typing a message"
|
||||
/>
|
||||
<img src="assets/images/typing.gif" alt="Agent is typing a message" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -30,7 +27,8 @@ export default {
|
||||
|
||||
<!-- Add "scoped" attribute to limit CSS to this component only -->
|
||||
<style lang="scss" scoped>
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
@import 'widget/assets/scss/variables.scss';
|
||||
|
||||
.agent-message-wrap {
|
||||
position: sticky;
|
||||
bottom: $space-smaller;
|
||||
|
||||
@@ -31,7 +31,7 @@ export default {
|
||||
<h3 class="mb-0 text-sm font-medium text-slate-800 dark:text-slate-50">
|
||||
{{ title }}
|
||||
</h3>
|
||||
<ArticleList :articles="articles" @click="onArticleClick" />
|
||||
<ArticleList :articles="articles" @select-article="onArticleClick" />
|
||||
<button
|
||||
class="inline-flex items-center justify-between px-2 py-1 -ml-2 text-sm font-medium leading-6 rounded-md text-slate-800 dark:text-slate-50 hover:bg-slate-25 dark:hover:bg-slate-800 see-articles"
|
||||
:style="{ color: widgetColor }"
|
||||
|
||||
@@ -16,7 +16,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
onClick(link) {
|
||||
this.$emit('click', link);
|
||||
this.$emit('selectArticle', link);
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -29,7 +29,7 @@ export default {
|
||||
:key="article.slug"
|
||||
:link="article.link"
|
||||
:title="article.title"
|
||||
@click="onClick"
|
||||
@select-article="onClick"
|
||||
/>
|
||||
</ul>
|
||||
</template>
|
||||
|
||||
@@ -18,7 +18,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$emit('click', this.link);
|
||||
this.$emit('selectArticle', this.link);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -32,16 +32,19 @@ export default {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
@import 'widget/assets/scss/variables.scss';
|
||||
|
||||
.banner {
|
||||
color: $color-white;
|
||||
font-size: $font-size-default;
|
||||
font-weight: $font-weight-bold;
|
||||
padding: $space-slab;
|
||||
text-align: center;
|
||||
|
||||
&.success {
|
||||
background: $color-success;
|
||||
}
|
||||
|
||||
&.error {
|
||||
background: $color-error;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import { DirectUpload } from 'activestorage';
|
||||
import { mapGetters } from 'vuex';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
|
||||
export default {
|
||||
components: { FluentIcon, FileUpload, Spinner },
|
||||
@@ -78,7 +79,7 @@ export default {
|
||||
|
||||
upload.create((error, blob) => {
|
||||
if (error) {
|
||||
this.$emitter.emit(BUS_EVENTS.SHOW_ALERT, {
|
||||
emitter.emit(BUS_EVENTS.SHOW_ALERT, {
|
||||
message: error,
|
||||
});
|
||||
} else {
|
||||
@@ -89,7 +90,7 @@ export default {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.$emitter.emit(BUS_EVENTS.SHOW_ALERT, {
|
||||
emitter.emit(BUS_EVENTS.SHOW_ALERT, {
|
||||
message: this.$t('FILE_SIZE_LIMIT', {
|
||||
MAXIMUM_FILE_UPLOAD_SIZE: this.fileUploadSizeLimit,
|
||||
}),
|
||||
@@ -112,7 +113,7 @@ export default {
|
||||
...this.getLocalFileAttributes(file),
|
||||
});
|
||||
} else {
|
||||
this.$emitter.emit(BUS_EVENTS.SHOW_ALERT, {
|
||||
emitter.emit(BUS_EVENTS.SHOW_ALERT, {
|
||||
message: this.$t('FILE_SIZE_LIMIT', {
|
||||
MAXIMUM_FILE_UPLOAD_SIZE: this.fileUploadSizeLimit,
|
||||
}),
|
||||
|
||||
@@ -9,6 +9,7 @@ import { sendEmailTranscript } from 'widget/api/conversation';
|
||||
import routerMixin from 'widget/mixins/routerMixin';
|
||||
import { IFrameHelper } from '../helpers/utils';
|
||||
import { CHATWOOT_ON_START_CONVERSATION } from '../constants/sdkEvents';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
@@ -51,7 +52,7 @@ export default {
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$emitter.on(BUS_EVENTS.TOGGLE_REPLY_TO_MESSAGE, this.toggleReplyTo);
|
||||
emitter.on(BUS_EVENTS.TOGGLE_REPLY_TO_MESSAGE, this.toggleReplyTo);
|
||||
},
|
||||
methods: {
|
||||
...mapActions('conversation', [
|
||||
@@ -99,12 +100,12 @@ export default {
|
||||
if (this.hasEmail) {
|
||||
try {
|
||||
await sendEmailTranscript();
|
||||
this.$emitter.emit(BUS_EVENTS.SHOW_ALERT, {
|
||||
emitter.emit(BUS_EVENTS.SHOW_ALERT, {
|
||||
message: this.$t('EMAIL_TRANSCRIPT.SEND_EMAIL_SUCCESS'),
|
||||
type: 'success',
|
||||
});
|
||||
} catch (error) {
|
||||
this.$emitter.$emit(BUS_EVENTS.SHOW_ALERT, {
|
||||
emitter.$emit(BUS_EVENTS.SHOW_ALERT, {
|
||||
message: this.$t('EMAIL_TRANSCRIPT.SEND_EMAIL_ERROR'),
|
||||
});
|
||||
}
|
||||
@@ -157,7 +158,7 @@ export default {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
@import 'widget/assets/scss/variables.scss';
|
||||
|
||||
.branding {
|
||||
align-items: center;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script>
|
||||
import { defineAsyncComponent } from 'vue';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
import ChatAttachmentButton from 'widget/components/ChatAttachment.vue';
|
||||
@@ -8,7 +9,9 @@ import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import ResizableTextArea from 'shared/components/ResizableTextArea.vue';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
|
||||
const EmojiInput = () => import('shared/components/emoji/EmojiInput.vue');
|
||||
const EmojiInput = defineAsyncComponent(
|
||||
() => import('shared/components/emoji/EmojiInput.vue')
|
||||
);
|
||||
|
||||
export default {
|
||||
name: 'ChatInputWrap',
|
||||
@@ -173,16 +176,16 @@ export default {
|
||||
/>
|
||||
<ChatSendButton
|
||||
v-if="showSendButton"
|
||||
:on-click="handleButtonClick"
|
||||
:color="widgetColor"
|
||||
@click="handleButtonClick"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
@import '~widget/assets/scss/mixins.scss';
|
||||
@import 'widget/assets/scss/variables.scss';
|
||||
@import 'widget/assets/scss/mixins.scss';
|
||||
|
||||
.chat-message--input {
|
||||
align-items: center;
|
||||
|
||||
@@ -55,7 +55,7 @@ export default {
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
@import 'widget/assets/scss/variables.scss';
|
||||
|
||||
.chat-bubble .message-content,
|
||||
.chat-bubble.user {
|
||||
|
||||
@@ -16,10 +16,6 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
onClick: {
|
||||
type: Function,
|
||||
default: () => {},
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#6e6f73',
|
||||
@@ -33,7 +29,6 @@ export default {
|
||||
type="submit"
|
||||
:disabled="disabled"
|
||||
class="icon-button flex items-center justify-center ml-1"
|
||||
@click="onClick"
|
||||
>
|
||||
<FluentIcon v-if="!loading" icon="send" :style="`color: ${color}`" />
|
||||
<Spinner v-else size="small" />
|
||||
|
||||
@@ -122,8 +122,8 @@ export default {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
@import '~widget/assets/scss/mixins.scss';
|
||||
@import 'widget/assets/scss/variables.scss';
|
||||
@import 'widget/assets/scss/mixins.scss';
|
||||
|
||||
.conversation--container {
|
||||
display: flex;
|
||||
@@ -135,6 +135,7 @@ export default {
|
||||
&.light-scheme {
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
&.dark-scheme {
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ export default {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
@import 'widget/assets/scss/variables.scss';
|
||||
|
||||
.file {
|
||||
.icon-wrap {
|
||||
@@ -115,6 +115,7 @@ export default {
|
||||
.link-wrap {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.meta {
|
||||
padding-right: $space-smaller;
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import wootInput from './Input';
|
||||
|
||||
export default {
|
||||
title: 'Components/Form/Input',
|
||||
component: wootInput,
|
||||
argTypes: {
|
||||
label: {
|
||||
defaultValue: 'Email Address',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
type: {
|
||||
defaultValue: 'email',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
placeholder: {
|
||||
defaultValue: 'Please enter your email address',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
value: {
|
||||
defaultValue: 'John12@ync.in',
|
||||
control: {
|
||||
type: 'text ,number',
|
||||
},
|
||||
},
|
||||
error: {
|
||||
defaultValue: '',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { wootInput },
|
||||
template: '<woot-input v-bind="$props" @input="onClick"></woot-input>',
|
||||
});
|
||||
|
||||
export const Input = Template.bind({});
|
||||
Input.args = {
|
||||
onClick: action('Added'),
|
||||
};
|
||||
@@ -1,238 +1,227 @@
|
||||
<script>
|
||||
import countries from 'shared/constants/countries.js';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import FormulateInputMixin from '@braid/vue-formulate/src/FormulateInputMixin';
|
||||
<script setup>
|
||||
import { ref, computed, watch, useTemplateRef, nextTick, unref } from 'vue';
|
||||
import countriesList from 'shared/constants/countries.js';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import {
|
||||
getActiveCountryCode,
|
||||
getActiveDialCode,
|
||||
} from 'shared/components/PhoneInput/helper';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
FluentIcon,
|
||||
const { context } = defineProps({
|
||||
context: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
mixins: [FormulateInputMixin],
|
||||
props: {
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
hasErrorInPhoneInput: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return { getThemeClass };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedIndex: -1,
|
||||
showDropdown: false,
|
||||
searchCountry: '',
|
||||
activeCountryCode: getActiveCountryCode(),
|
||||
activeDialCode: getActiveDialCode(),
|
||||
phoneNumber: '',
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
countries() {
|
||||
return [
|
||||
{
|
||||
name: this.dropdownFirstItemName,
|
||||
dial_code: '',
|
||||
emoji: '',
|
||||
id: '',
|
||||
},
|
||||
...countries,
|
||||
];
|
||||
},
|
||||
dropdownFirstItemName() {
|
||||
return this.activeCountryCode ? 'Clear selection' : 'Select Country';
|
||||
},
|
||||
dropdownClass() {
|
||||
return `${this.getThemeClass(
|
||||
'bg-slate-100',
|
||||
'dark:bg-slate-700'
|
||||
)} ${this.getThemeClass('text-slate-700', 'dark:text-slate-50')}`;
|
||||
},
|
||||
dropdownBackgroundClass() {
|
||||
return `${this.getThemeClass(
|
||||
'bg-white',
|
||||
'dark:bg-slate-700'
|
||||
)} ${this.getThemeClass('text-slate-700', 'dark:text-slate-50')}`;
|
||||
},
|
||||
dropdownItemClass() {
|
||||
return `${this.getThemeClass(
|
||||
'text-slate-700',
|
||||
'dark:text-slate-50'
|
||||
)} ${this.getThemeClass('hover:bg-slate-50', 'dark:hover:bg-slate-600')}`;
|
||||
},
|
||||
activeDropdownItemClass() {
|
||||
return `active ${this.getThemeClass(
|
||||
'bg-slate-100',
|
||||
'dark:bg-slate-800'
|
||||
)}`;
|
||||
},
|
||||
focusedDropdownItemClass() {
|
||||
return `focus ${this.getThemeClass('bg-slate-50', 'dark:bg-slate-600')}`;
|
||||
},
|
||||
inputHasError() {
|
||||
return this.hasErrorInPhoneInput
|
||||
? `border-red-200 hover:border-red-300 focus:border-red-300 ${this.inputLightAndDarkModeColor}`
|
||||
: `hover:border-black-300 focus:border-black-300 ${this.inputLightAndDarkModeColor} ${this.inputBorderColor}`;
|
||||
},
|
||||
inputBorderColor() {
|
||||
return `${this.getThemeClass(
|
||||
'border-black-200',
|
||||
'dark:border-black-500'
|
||||
)}`;
|
||||
},
|
||||
inputLightAndDarkModeColor() {
|
||||
return `${this.getThemeClass(
|
||||
'bg-white',
|
||||
'dark:bg-slate-600'
|
||||
)} ${this.getThemeClass('text-slate-700', 'dark:text-slate-50')}`;
|
||||
},
|
||||
items() {
|
||||
return this.countries.filter(country => {
|
||||
const { name, dial_code, id } = country;
|
||||
const search = this.searchCountry.toLowerCase();
|
||||
return (
|
||||
name.toLowerCase().includes(search) ||
|
||||
dial_code.toLowerCase().includes(search) ||
|
||||
id.toLowerCase().includes(search)
|
||||
);
|
||||
});
|
||||
},
|
||||
activeCountry() {
|
||||
return (
|
||||
this.countries.find(country => country.id === this.activeCountryCode) ||
|
||||
''
|
||||
);
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
items(newItems) {
|
||||
if (newItems.length < this.selectedIndex + 1) {
|
||||
// Reset the selected index to 0 if the new items length is less than the selected index.
|
||||
this.selectedIndex = 0;
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
setContextValue(code) {
|
||||
// This function is used to set the context value.
|
||||
// The context value is used to set the value of the phone number field in the pre-chat form.
|
||||
this.context.model = `${code}${this.phoneNumber}`;
|
||||
},
|
||||
dynamicallySetCountryCode(value) {
|
||||
// This function is used to set the country code dynamically.
|
||||
// The country and dial code is used to set from the value of the phone number field in the pre-chat form.
|
||||
if (!value) return;
|
||||
});
|
||||
|
||||
// check the number first four digit and check weather it is available in the countries array or not.
|
||||
const country = countries.find(code => value.startsWith(code.dial_code));
|
||||
if (country) {
|
||||
// if it is available then set the country code and dial code.
|
||||
this.activeCountryCode = country.id;
|
||||
this.activeDialCode = country.dial_code;
|
||||
// set the phone number without dial code.
|
||||
this.phoneNumber = value.replace(country.dial_code, '');
|
||||
}
|
||||
},
|
||||
onChange(e) {
|
||||
this.phoneNumber = e.target.value;
|
||||
this.dynamicallySetCountryCode(this.phoneNumber);
|
||||
// This function is used to set the context value when the user types in the phone number field.
|
||||
this.setContextValue(this.activeDialCode);
|
||||
},
|
||||
dropdownItem() {
|
||||
// This function is used to get all the items in the dropdown.
|
||||
if (!this.showDropdown) return [];
|
||||
return Array.from(
|
||||
this.$refs.dropdown?.querySelectorAll(
|
||||
'div.country-dropdown div.country-dropdown--item'
|
||||
)
|
||||
);
|
||||
},
|
||||
focusedOrActiveItem(className) {
|
||||
// This function is used to get the focused or active item in the dropdown.
|
||||
if (!this.showDropdown) return [];
|
||||
return Array.from(
|
||||
this.$refs.dropdown?.querySelectorAll(
|
||||
`div.country-dropdown div.country-dropdown--item.${className}`
|
||||
)
|
||||
);
|
||||
},
|
||||
adjustScroll() {
|
||||
this.$nextTick(() => {
|
||||
this.scrollToFocusedOrActiveItem(this.focusedOrActiveItem('focus'));
|
||||
});
|
||||
},
|
||||
adjustSelection(direction) {
|
||||
if (!this.showDropdown) return;
|
||||
const maxIndex = this.items.length - 1;
|
||||
if (direction === 'up') {
|
||||
this.selectedIndex =
|
||||
this.selectedIndex <= 0 ? maxIndex : this.selectedIndex - 1;
|
||||
} else if (direction === 'down') {
|
||||
this.selectedIndex =
|
||||
this.selectedIndex >= maxIndex ? 0 : this.selectedIndex + 1;
|
||||
}
|
||||
this.adjustScroll();
|
||||
},
|
||||
moveSelectionUp() {
|
||||
this.adjustSelection('up');
|
||||
},
|
||||
moveSelectionDown() {
|
||||
this.adjustSelection('down');
|
||||
},
|
||||
onSelect() {
|
||||
if (!this.showDropdown || this.selectedIndex === -1) return;
|
||||
this.onSelectCountry(this.items[this.selectedIndex]);
|
||||
},
|
||||
scrollToFocusedOrActiveItem(item) {
|
||||
// This function is used to scroll the dropdown to the focused or active item.
|
||||
const focusedOrActiveItem = item;
|
||||
if (focusedOrActiveItem.length > 0) {
|
||||
const dropdown = this.$refs.dropdown;
|
||||
const dropdownHeight = dropdown.clientHeight;
|
||||
const itemTop = focusedOrActiveItem[0].offsetTop;
|
||||
const itemHeight = focusedOrActiveItem[0].offsetHeight;
|
||||
const scrollPosition = itemTop - dropdownHeight / 2 + itemHeight / 2;
|
||||
dropdown.scrollTo({
|
||||
top: scrollPosition,
|
||||
behavior: 'auto',
|
||||
});
|
||||
}
|
||||
},
|
||||
onSelectCountry(country) {
|
||||
this.activeCountryCode = country.id;
|
||||
this.searchCountry = '';
|
||||
this.activeDialCode = country.dial_code ? country.dial_code : '';
|
||||
this.setContextValue(country.dial_code);
|
||||
this.closeDropdown();
|
||||
},
|
||||
toggleCountryDropdown() {
|
||||
this.showDropdown = !this.showDropdown;
|
||||
this.selectedIndex = -1;
|
||||
if (this.showDropdown) {
|
||||
this.$nextTick(() => {
|
||||
this.$refs.searchbar.focus();
|
||||
// This is used to scroll the dropdown to the active item.
|
||||
this.scrollToFocusedOrActiveItem(this.focusedOrActiveItem('active'));
|
||||
});
|
||||
}
|
||||
},
|
||||
closeDropdown() {
|
||||
this.selectedIndex = -1;
|
||||
this.showDropdown = false;
|
||||
},
|
||||
const localValue = ref(context.value || '');
|
||||
|
||||
const { getThemeClass: $dm } = useDarkMode();
|
||||
|
||||
const selectedIndex = ref(-1);
|
||||
const showDropdown = ref(false);
|
||||
const searchCountry = ref('');
|
||||
const activeCountryCode = ref(getActiveCountryCode());
|
||||
const activeDialCode = ref(getActiveDialCode());
|
||||
const phoneNumber = ref('');
|
||||
|
||||
const dropdownRef = useTemplateRef('dropdown');
|
||||
const searchbarRef = useTemplateRef('searchbar');
|
||||
|
||||
const placeholder = computed(() => context?.attrs?.placeholder || '');
|
||||
const hasErrorInPhoneInput = computed(() => context.hasErrorInPhoneInput);
|
||||
const dropdownFirstItemName = computed(() =>
|
||||
activeCountryCode.value ? 'Clear selection' : 'Select Country'
|
||||
);
|
||||
const countries = computed(() => [
|
||||
{
|
||||
name: dropdownFirstItemName.value,
|
||||
dial_code: '',
|
||||
emoji: '',
|
||||
id: '',
|
||||
},
|
||||
};
|
||||
...countriesList,
|
||||
]);
|
||||
|
||||
const dropdownClass = computed(() =>
|
||||
$dm('bg-slate-100 text-slate-700', 'dark:bg-slate-700 dark:text-slate-50')
|
||||
);
|
||||
|
||||
const dropdownBackgroundClass = computed(() =>
|
||||
$dm('bg-white text-slate-700', 'dark:bg-slate-700 dark:text-slate-50')
|
||||
);
|
||||
|
||||
const dropdownItemClass = computed(() =>
|
||||
$dm(
|
||||
'text-slate-700 hover:bg-slate-50',
|
||||
'dark:text-slate-50 dark:hover:bg-slate-600'
|
||||
)
|
||||
);
|
||||
|
||||
const activeDropdownItemClass = computed(
|
||||
() => `active ${$dm('bg-slate-100', 'dark:bg-slate-800')}`
|
||||
);
|
||||
|
||||
const focusedDropdownItemClass = computed(
|
||||
() => `focus ${$dm('bg-slate-50', 'dark:bg-slate-600')}`
|
||||
);
|
||||
|
||||
const inputLightAndDarkModeColor = computed(() =>
|
||||
$dm('bg-white text-slate-700', 'dark:bg-slate-600 dark:text-slate-50')
|
||||
);
|
||||
|
||||
const inputBorderColor = computed(
|
||||
() => `${$dm('border-black-200', 'dark:border-black-500')}`
|
||||
);
|
||||
|
||||
const inputHasError = computed(() =>
|
||||
hasErrorInPhoneInput.value
|
||||
? `border-red-200 hover:border-red-300 focus:border-red-300 ${inputLightAndDarkModeColor.value}`
|
||||
: `hover:border-black-300 focus:border-black-300 ${inputLightAndDarkModeColor.value} ${inputBorderColor.value}`
|
||||
);
|
||||
|
||||
const items = computed(() => {
|
||||
return countries.value.filter(country => {
|
||||
const { name, dial_code, id } = country;
|
||||
const search = searchCountry.value.toLowerCase();
|
||||
return (
|
||||
name.toLowerCase().includes(search) ||
|
||||
dial_code.toLowerCase().includes(search) ||
|
||||
id.toLowerCase().includes(search)
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const activeCountry = computed(() => {
|
||||
return countries.value.find(
|
||||
country => country.id === activeCountryCode.value
|
||||
);
|
||||
});
|
||||
|
||||
watch(items, newItems => {
|
||||
if (newItems.length < selectedIndex.value + 1) {
|
||||
// Reset the selected index to 0 if the new items length is less than the selected index.
|
||||
selectedIndex.value = 0;
|
||||
}
|
||||
});
|
||||
|
||||
function setContextValue(code) {
|
||||
const safeCode = unref(code);
|
||||
// This function is used to set the context value.
|
||||
// The context value is used to set the value of the phone number field in the pre-chat form.
|
||||
localValue.value = `${safeCode}${phoneNumber.value}`;
|
||||
context.node.input(localValue.value);
|
||||
}
|
||||
|
||||
function dynamicallySetCountryCode(value) {
|
||||
const safeValue = unref(value);
|
||||
// This function is used to set the country code dynamically.
|
||||
// The country and dial code is used to set from the value of the phone number field in the pre-chat form.
|
||||
if (!safeValue) return;
|
||||
|
||||
// check the number first four digit and check weather it is available in the countries array or not.
|
||||
const country = countries.value.find(code =>
|
||||
safeValue.startsWith(code.dial_code)
|
||||
);
|
||||
|
||||
if (country) {
|
||||
// if it is available then set the country code and dial code.
|
||||
activeCountryCode.value = country.id;
|
||||
activeDialCode.value = country.dial_code;
|
||||
// set the phone number without dial code.
|
||||
phoneNumber.value = safeValue.replace(country.dial_code, '');
|
||||
}
|
||||
}
|
||||
|
||||
function onChange(e) {
|
||||
phoneNumber.value = e.target.value;
|
||||
dynamicallySetCountryCode(phoneNumber);
|
||||
// This function is used to set the context value when the user types in the phone number field.
|
||||
setContextValue(activeDialCode);
|
||||
}
|
||||
|
||||
function focusedOrActiveItem(className) {
|
||||
// This function is used to get the focused or active item in the dropdown.
|
||||
if (!showDropdown.value) return [];
|
||||
return Array.from(
|
||||
dropdownRef.value?.querySelectorAll(
|
||||
`div.country-dropdown div.country-dropdown--item.${className}`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function scrollToFocusedOrActiveItem(item) {
|
||||
// This function is used to scroll the dropdown to the focused or active item.
|
||||
const focusedOrActiveItemLocal = item;
|
||||
if (focusedOrActiveItemLocal.length > 0) {
|
||||
const dropdown = dropdownRef.value;
|
||||
const dropdownHeight = dropdown.clientHeight;
|
||||
const itemTop = focusedOrActiveItem[0].offsetTop;
|
||||
const itemHeight = focusedOrActiveItem[0].offsetHeight;
|
||||
const scrollPosition = itemTop - dropdownHeight / 2 + itemHeight / 2;
|
||||
dropdown.scrollTo({
|
||||
top: scrollPosition,
|
||||
behavior: 'auto',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function adjustScroll() {
|
||||
nextTick(() => {
|
||||
scrollToFocusedOrActiveItem(focusedOrActiveItem('focus'));
|
||||
});
|
||||
}
|
||||
|
||||
function adjustSelection(direction) {
|
||||
if (!showDropdown.value) return;
|
||||
const maxIndex = items.value.length - 1;
|
||||
if (direction === 'up') {
|
||||
selectedIndex.value =
|
||||
selectedIndex.value <= 0 ? maxIndex : selectedIndex.value - 1;
|
||||
} else if (direction === 'down') {
|
||||
selectedIndex.value =
|
||||
selectedIndex.value >= maxIndex ? 0 : selectedIndex.value + 1;
|
||||
}
|
||||
adjustScroll();
|
||||
}
|
||||
|
||||
function moveSelectionUp() {
|
||||
adjustSelection('up');
|
||||
}
|
||||
function moveSelectionDown() {
|
||||
adjustSelection('down');
|
||||
}
|
||||
|
||||
function closeDropdown() {
|
||||
selectedIndex.value = -1;
|
||||
showDropdown.value = false;
|
||||
}
|
||||
|
||||
function onSelectCountry(country) {
|
||||
activeCountryCode.value = country.id;
|
||||
searchCountry.value = '';
|
||||
activeDialCode.value = country.dial_code ? country.dial_code : '';
|
||||
setContextValue(country.dial_code);
|
||||
closeDropdown();
|
||||
}
|
||||
|
||||
function toggleCountryDropdown() {
|
||||
showDropdown.value = !showDropdown.value;
|
||||
selectedIndex.value = -1;
|
||||
if (showDropdown.value) {
|
||||
nextTick(() => {
|
||||
searchbarRef.value.focus();
|
||||
// This is used to scroll the dropdown to the active item.
|
||||
scrollToFocusedOrActiveItem(focusedOrActiveItem('active'));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function onSelect() {
|
||||
if (!showDropdown.value || selectedIndex.value === -1) return;
|
||||
onSelectCountry(items.value[selectedIndex.value]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -255,7 +244,7 @@ export default {
|
||||
<span
|
||||
v-if="activeDialCode"
|
||||
class="py-2 pl-2 pr-0 text-base"
|
||||
:class="getThemeClass('text-slate-700', 'dark:text-slate-50')"
|
||||
:class="$dm('text-slate-700', 'dark:text-slate-50')"
|
||||
>
|
||||
{{ activeDialCode }}
|
||||
</span>
|
||||
@@ -282,15 +271,12 @@ export default {
|
||||
>
|
||||
<div class="sticky top-0" :class="dropdownBackgroundClass">
|
||||
<input
|
||||
ref="searchbar"
|
||||
v-model="searchCountry"
|
||||
ref="searchbar"
|
||||
type="text"
|
||||
:placeholder="$t('PRE_CHAT_FORM.FIELDS.PHONE_NUMBER.DROPDOWN_SEARCH')"
|
||||
class="w-full h-8 px-3 py-2 mt-1 mb-1 text-sm border border-solid rounded outline-none dropdown-search"
|
||||
:class="[
|
||||
getThemeClass('bg-slate-50', 'dark:bg-slate-600'),
|
||||
inputBorderColor,
|
||||
]"
|
||||
:class="[$dm('bg-slate-50', 'dark:bg-slate-600'), inputBorderColor]"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
@@ -315,7 +301,7 @@ export default {
|
||||
<div v-if="items.length === 0">
|
||||
<span
|
||||
class="flex justify-center mt-4 text-sm text-center"
|
||||
:class="getThemeClass('text-slate-700', 'dark:text-slate-50')"
|
||||
:class="$dm('text-slate-700', 'dark:text-slate-50')"
|
||||
>
|
||||
{{ $t('PRE_CHAT_FORM.FIELDS.PHONE_NUMBER.DROPDOWN_EMPTY') }}
|
||||
</span>
|
||||
@@ -325,7 +311,7 @@ export default {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
@import 'widget/assets/scss/variables.scss';
|
||||
|
||||
.phone-input--wrap {
|
||||
.phone-input {
|
||||
|
||||
@@ -1,51 +0,0 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import wootTextArea from './TextArea';
|
||||
|
||||
export default {
|
||||
title: 'Components/Form/Text Area',
|
||||
component: wootTextArea,
|
||||
argTypes: {
|
||||
label: {
|
||||
defaultValue: 'Message',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
type: {
|
||||
defaultValue: '',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
placeholder: {
|
||||
defaultValue: 'Please enter your message',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
value: {
|
||||
defaultValue: 'Lorem ipsum is a placeholder text commonly used',
|
||||
control: {
|
||||
type: 'text ,number',
|
||||
},
|
||||
},
|
||||
error: {
|
||||
defaultValue: '',
|
||||
control: {
|
||||
type: 'text',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { wootTextArea },
|
||||
template:
|
||||
'<woot-text-area v-bind="$props" @input="onClick"></woot-text-area>',
|
||||
});
|
||||
|
||||
export const TextArea = Template.bind({});
|
||||
TextArea.args = {
|
||||
onClick: action('Added'),
|
||||
};
|
||||
@@ -127,7 +127,7 @@ export default {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
@import 'widget/assets/scss/variables.scss';
|
||||
|
||||
.actions {
|
||||
button {
|
||||
@@ -142,6 +142,7 @@ export default {
|
||||
.close-button {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.rn-close-button {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
@@ -9,9 +9,6 @@ export default {
|
||||
onImgError() {
|
||||
this.$emit('error');
|
||||
},
|
||||
onClick() {
|
||||
this.$emit('click');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
@@ -24,19 +21,14 @@ export default {
|
||||
class="image"
|
||||
>
|
||||
<div class="wrap">
|
||||
<img
|
||||
:src="thumb"
|
||||
alt="Picture message"
|
||||
@click="onClick"
|
||||
@error="onImgError"
|
||||
/>
|
||||
<img :src="thumb" alt="Picture message" @error="onImgError" />
|
||||
<span class="time">{{ readableTime }}</span>
|
||||
</div>
|
||||
</a>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
@import 'widget/assets/scss/variables.scss';
|
||||
|
||||
.image {
|
||||
display: block;
|
||||
|
||||
@@ -10,7 +10,6 @@ export default {
|
||||
<template>
|
||||
<button
|
||||
class="p-1 mb-1 rounded-full dark:text-slate-500 dark:bg-slate-900 text-slate-600 bg-slate-100 hover:text-slate-800"
|
||||
@click="$emit('click')"
|
||||
>
|
||||
<FluentIcon icon="arrow-reply" size="11" class="flex-shrink-0" />
|
||||
</button>
|
||||
|
||||
@@ -9,11 +9,14 @@ import { useMessageFormatter } from 'shared/composables/useMessageFormatter';
|
||||
import routerMixin from 'widget/mixins/routerMixin';
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
import configMixin from 'widget/mixins/configMixin';
|
||||
import { FormKit, createInput } from '@formkit/vue';
|
||||
import PhoneInput from 'widget/components/Form/PhoneInput.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
CustomButton,
|
||||
Spinner,
|
||||
FormKit,
|
||||
},
|
||||
mixins: [routerMixin, configMixin],
|
||||
props: {
|
||||
@@ -23,9 +26,13 @@ export default {
|
||||
},
|
||||
},
|
||||
setup() {
|
||||
const phoneInput = createInput(PhoneInput, {
|
||||
props: ['hasErrorInPhoneInput'],
|
||||
});
|
||||
const { formatMessage } = useMessageFormatter();
|
||||
const { getThemeClass } = useDarkMode();
|
||||
return { formatMessage, getThemeClass };
|
||||
|
||||
return { formatMessage, phoneInput, getThemeClass };
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
@@ -98,7 +105,7 @@ export default {
|
||||
...field,
|
||||
type:
|
||||
field.name === 'phoneNumber'
|
||||
? 'phoneInput'
|
||||
? this.phoneInput
|
||||
: this.findFieldType(field.type),
|
||||
}));
|
||||
},
|
||||
@@ -156,8 +163,9 @@ export default {
|
||||
'dark:text-red-400'
|
||||
)}`;
|
||||
},
|
||||
inputClass(context) {
|
||||
const { hasErrors, classification, type } = context;
|
||||
inputClass(input) {
|
||||
const { state, family: classification, type } = input.context;
|
||||
const hasErrors = state.invalid;
|
||||
if (classification === 'box' && type === 'checkbox') {
|
||||
return '';
|
||||
}
|
||||
@@ -201,9 +209,7 @@ export default {
|
||||
};
|
||||
const validationKeys = Object.keys(validations);
|
||||
const isRequired = this.isContactFieldRequired(name);
|
||||
const validation = isRequired
|
||||
? ['bail', 'required']
|
||||
: ['bail', 'optional'];
|
||||
const validation = isRequired ? ['required'] : ['optional'];
|
||||
|
||||
if (
|
||||
validationKeys.includes(name) ||
|
||||
@@ -212,10 +218,13 @@ export default {
|
||||
) {
|
||||
const validationType =
|
||||
validations[type] || validations[name] || validations[field_type];
|
||||
return validationType ? validation.concat(validationType) : validation;
|
||||
const allValidations = validationType
|
||||
? validation.concat(validationType)
|
||||
: validation;
|
||||
return allValidations.join('|');
|
||||
}
|
||||
|
||||
return [];
|
||||
return '';
|
||||
},
|
||||
findFieldType(type) {
|
||||
if (type === 'link') {
|
||||
@@ -238,7 +247,7 @@ export default {
|
||||
});
|
||||
return values;
|
||||
}
|
||||
return null;
|
||||
return {};
|
||||
},
|
||||
onSubmit() {
|
||||
const { emailAddress, fullName, phoneNumber, message } = this.formValues;
|
||||
@@ -257,10 +266,16 @@ export default {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<FormulateForm
|
||||
<!-- hide the default submit button for now -->
|
||||
<FormKit
|
||||
v-model="formValues"
|
||||
class="flex flex-col flex-1 p-6 overflow-y-auto"
|
||||
@submit="onSubmit"
|
||||
type="form"
|
||||
form-class="flex flex-col flex-1 w-full p-6 overflow-y-auto"
|
||||
:incomplete-message="false"
|
||||
:submit-attrs="{
|
||||
inputClass: 'hidden',
|
||||
wrapperClass: 'hidden',
|
||||
}"
|
||||
>
|
||||
<div
|
||||
v-if="shouldShowHeaderMessage"
|
||||
@@ -268,7 +283,10 @@ export default {
|
||||
class="mb-4 text-sm leading-5 pre-chat-header-message"
|
||||
:class="getThemeClass('text-black-800', 'dark:text-slate-50')"
|
||||
/>
|
||||
<FormulateInput
|
||||
<!-- Why do the v-bind shenanigan? Because Formkit API is really bad.
|
||||
If we just pass the options as is even with null or undefined or false,
|
||||
it assumes we are trying to make a multicheckbox. This is the best we have for now -->
|
||||
<FormKit
|
||||
v-for="item in enabledPreChatFields"
|
||||
:key="item.name"
|
||||
:name="item.name"
|
||||
@@ -276,7 +294,13 @@ export default {
|
||||
:label="getLabel(item)"
|
||||
:placeholder="getPlaceHolder(item)"
|
||||
:validation="getValidation(item)"
|
||||
:options="getOptions(item)"
|
||||
v-bind="
|
||||
item.type === 'select'
|
||||
? {
|
||||
options: getOptions(item),
|
||||
}
|
||||
: undefined
|
||||
"
|
||||
:label-class="context => labelClass(context)"
|
||||
:input-class="context => inputClass(context)"
|
||||
:validation-messages="{
|
||||
@@ -292,7 +316,7 @@ export default {
|
||||
}"
|
||||
:has-error-in-phone-input="hasErrorInPhoneInput"
|
||||
/>
|
||||
<FormulateInput
|
||||
<FormKit
|
||||
v-if="!hasActiveCampaign"
|
||||
name="message"
|
||||
type="textarea"
|
||||
@@ -316,44 +340,30 @@ export default {
|
||||
<Spinner v-if="isCreating" class="p-0" />
|
||||
{{ $t('START_CONVERSATION') }}
|
||||
</CustomButton>
|
||||
</FormulateForm>
|
||||
</FormKit>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
::v-deep {
|
||||
.wrapper[data-type='checkbox'] {
|
||||
.formulate-input-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
line-height: $space-normal;
|
||||
<style lang="scss">
|
||||
.formkit-outer {
|
||||
@apply mt-2;
|
||||
}
|
||||
|
||||
label {
|
||||
margin-left: 0.2rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.wrapper {
|
||||
.formulate-input-element--date,
|
||||
.formulate-input-element--checkbox {
|
||||
input {
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.wrapper[data-type='textarea'] {
|
||||
.formulate-input-element--textarea {
|
||||
textarea {
|
||||
min-height: 8rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
[data-invalid] .formkit-message {
|
||||
@apply text-red-500 block text-xs font-normal mb-1 w-full;
|
||||
}
|
||||
|
||||
.formkit-outer[data-type='checkbox'] .formkit-wrapper {
|
||||
@apply flex items-center gap-2 px-0.5;
|
||||
}
|
||||
|
||||
.formkit-messages {
|
||||
@apply list-none m-0 p-0;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
.pre-chat-header-message {
|
||||
.link {
|
||||
color: $color-woot;
|
||||
text-decoration: underline;
|
||||
@apply text-woot-500 underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ export default {
|
||||
},
|
||||
},
|
||||
|
||||
beforeDestroy() {
|
||||
unmounted() {
|
||||
clearTimeout(this.timeOutID);
|
||||
},
|
||||
methods: {
|
||||
|
||||
@@ -7,6 +7,8 @@ import {
|
||||
ON_CAMPAIGN_MESSAGE_CLICK,
|
||||
ON_UNREAD_MESSAGE_CLICK,
|
||||
} from '../constants/widgetBusEvents';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
|
||||
import { useDarkMode } from 'widget/composables/useDarkMode';
|
||||
export default {
|
||||
name: 'UnreadMessage',
|
||||
@@ -50,10 +52,9 @@ export default {
|
||||
},
|
||||
avatarUrl() {
|
||||
// eslint-disable-next-line
|
||||
const BotImage = require('dashboard/assets/images/chatwoot_bot.png');
|
||||
const displayImage = this.useInboxAvatarForBot
|
||||
? this.inboxAvatarUrl
|
||||
: BotImage;
|
||||
: '/assets/images/chatwoot_bot.png';
|
||||
if (this.isSenderExist(this.sender)) {
|
||||
const { avatar_url: avatarUrl } = this.sender;
|
||||
return avatarUrl;
|
||||
@@ -84,9 +85,9 @@ export default {
|
||||
},
|
||||
onClickMessage() {
|
||||
if (this.campaignId) {
|
||||
this.$emitter.emit(ON_CAMPAIGN_MESSAGE_CLICK, this.campaignId);
|
||||
emitter.emit(ON_CAMPAIGN_MESSAGE_CLICK, this.campaignId);
|
||||
} else {
|
||||
this.$emitter.emit(ON_UNREAD_MESSAGE_CLICK);
|
||||
emitter.emit(ON_UNREAD_MESSAGE_CLICK);
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -119,7 +120,8 @@ export default {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
@import 'widget/assets/scss/variables.scss';
|
||||
|
||||
.chat-bubble {
|
||||
max-width: 85%;
|
||||
padding: $space-normal;
|
||||
@@ -132,10 +134,12 @@ export default {
|
||||
text-align: left;
|
||||
padding-bottom: $space-small;
|
||||
font-size: $font-size-small;
|
||||
|
||||
.agent--name {
|
||||
font-weight: $font-weight-medium;
|
||||
margin-left: $space-smaller;
|
||||
}
|
||||
|
||||
.company--name {
|
||||
color: $color-light-gray;
|
||||
margin-left: $space-smaller;
|
||||
|
||||
@@ -5,6 +5,7 @@ import { ON_UNREAD_MESSAGE_CLICK } from '../constants/widgetBusEvents';
|
||||
import FluentIcon from 'shared/components/FluentIcon/Index.vue';
|
||||
import UnreadMessage from 'widget/components/UnreadMessage.vue';
|
||||
import { isWidgetColorLighter } from 'shared/helpers/colorHelper';
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
|
||||
export default {
|
||||
name: 'Unread',
|
||||
@@ -34,7 +35,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
openConversationView() {
|
||||
this.$emitter.emit(ON_UNREAD_MESSAGE_CLICK);
|
||||
emitter.emit(ON_UNREAD_MESSAGE_CLICK);
|
||||
},
|
||||
closeFullView() {
|
||||
this.$emit('close');
|
||||
@@ -100,7 +101,7 @@ export default {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~widget/assets/scss/variables';
|
||||
@import 'widget/assets/scss/variables';
|
||||
|
||||
.unread-wrap {
|
||||
width: 100%;
|
||||
@@ -147,6 +148,7 @@ export default {
|
||||
color: $color-body;
|
||||
}
|
||||
}
|
||||
|
||||
.is-background-light {
|
||||
color: $color-body !important;
|
||||
}
|
||||
|
||||
@@ -29,12 +29,12 @@ export default {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
@import '~widget/assets/scss/mixins.scss';
|
||||
@import 'widget/assets/scss/variables.scss';
|
||||
@import 'widget/assets/scss/mixins.scss';
|
||||
|
||||
.user-avatar {
|
||||
@include light-shadow;
|
||||
background: url('~widget/assets/images/defaultUser.png') center center
|
||||
background: url('widget/assets/images/defaultUser.png') center center
|
||||
no-repeat;
|
||||
background-size: cover;
|
||||
border-radius: 50%;
|
||||
|
||||
@@ -10,7 +10,7 @@ import messageMixin from '../mixins/messageMixin';
|
||||
import ReplyToChip from 'widget/components/ReplyToChip.vue';
|
||||
import DragWrapper from 'widget/components/DragWrapper.vue';
|
||||
import { BUS_EVENTS } from 'shared/constants/busEvents';
|
||||
|
||||
import { emitter } from 'shared/helpers/mitt';
|
||||
import { mapGetters } from 'vuex';
|
||||
|
||||
export default {
|
||||
@@ -97,7 +97,7 @@ export default {
|
||||
this.hasVideoError = true;
|
||||
},
|
||||
toggleReply() {
|
||||
this.$emitter.emit(BUS_EVENTS.TOGGLE_REPLY_TO_MESSAGE, this.message);
|
||||
emitter.emit(BUS_EVENTS.TOGGLE_REPLY_TO_MESSAGE, this.message);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -37,7 +37,7 @@ export default {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
@import 'widget/assets/scss/variables.scss';
|
||||
|
||||
.chat-bubble.user::v-deep {
|
||||
p code {
|
||||
|
||||
@@ -28,7 +28,7 @@ export default {
|
||||
mounted() {
|
||||
document.addEventListener('keydown', this.onEscape);
|
||||
},
|
||||
beforeDestroy() {
|
||||
unmounted() {
|
||||
document.removeEventListener('keydown', this.onEscape);
|
||||
},
|
||||
methods: {
|
||||
@@ -76,7 +76,7 @@ export default {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
@import 'widget/assets/scss/variables.scss';
|
||||
|
||||
.menu-content {
|
||||
width: max-content;
|
||||
|
||||
@@ -54,14 +54,16 @@ export default {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
@import 'widget/assets/scss/variables.scss';
|
||||
|
||||
.menu-item {
|
||||
margin-left: $zero !important;
|
||||
outline: none;
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
&:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export default {
|
||||
mounted() {
|
||||
this.$el.addEventListener('scroll', this.updateScrollPosition);
|
||||
},
|
||||
beforeDestroy() {
|
||||
unmounted() {
|
||||
this.$el.removeEventListener('scroll', this.updateScrollPosition);
|
||||
cancelAnimationFrame(this.requestID);
|
||||
},
|
||||
@@ -142,8 +142,8 @@ export default {
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
@import '~widget/assets/scss/variables';
|
||||
@import '~widget/assets/scss/mixins';
|
||||
@import 'widget/assets/scss/variables';
|
||||
@import 'widget/assets/scss/mixins';
|
||||
|
||||
.custom-header-shadow {
|
||||
@include shadow-large;
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { action } from '@storybook/addon-actions';
|
||||
import ArticleHero from '../ArticleHero.vue'; // adjust this path to match your file's location
|
||||
|
||||
export default {
|
||||
title: 'Components/Widgets/ArticleHero',
|
||||
component: ArticleHero,
|
||||
argTypes: {
|
||||
articles: { control: 'array', description: 'Array of articles' },
|
||||
},
|
||||
};
|
||||
|
||||
const Template = (args, { argTypes }) => ({
|
||||
props: Object.keys(argTypes),
|
||||
components: { ArticleHero },
|
||||
template:
|
||||
'<article-hero v-bind="$props" @view-all-articles="viewAllArticles" />',
|
||||
methods: {
|
||||
viewAllArticles: action('view-all-articles'),
|
||||
},
|
||||
});
|
||||
|
||||
export const Default = Template.bind({});
|
||||
Default.args = {
|
||||
articles: [
|
||||
{ title: 'Article 1', content: 'This is article 1.' },
|
||||
{ title: 'Article 2', content: 'This is article 2.' },
|
||||
{ title: 'Article 3', content: 'This is article 3.' },
|
||||
{ title: 'Article 4', content: 'This is article 4.' },
|
||||
],
|
||||
};
|
||||
@@ -52,7 +52,7 @@ export default {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
@import 'widget/assets/scss/variables.scss';
|
||||
|
||||
.article-item {
|
||||
border-bottom: 1px solid $color-border;
|
||||
|
||||
@@ -92,7 +92,7 @@ export default {
|
||||
@submit.prevent="onSubmit"
|
||||
>
|
||||
<input
|
||||
v-model.trim="email"
|
||||
v-model="email"
|
||||
class="form-input"
|
||||
:placeholder="$t('EMAIL_PLACEHOLDER')"
|
||||
:class="inputHasError"
|
||||
@@ -116,7 +116,7 @@ export default {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
@import 'widget/assets/scss/variables.scss';
|
||||
|
||||
.email-input-group {
|
||||
display: flex;
|
||||
|
||||
@@ -85,7 +85,7 @@ export default {
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import '~widget/assets/scss/variables.scss';
|
||||
@import 'widget/assets/scss/variables.scss';
|
||||
|
||||
.video-call--container {
|
||||
position: fixed;
|
||||
|
||||
Reference in New Issue
Block a user