mirror of
				https://github.com/lingble/chatwoot.git
				synced 2025-11-04 04:57:51 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			560 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
			
		
		
	
	
			560 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Vue
		
	
	
	
	
	
<template>
 | 
						|
  <div class="flex flex-col justify-between flex-grow h-full min-w-0 m-0">
 | 
						|
    <banner
 | 
						|
      v-if="!currentChat.can_reply"
 | 
						|
      color-scheme="alert"
 | 
						|
      :banner-message="replyWindowBannerMessage"
 | 
						|
      :href-link="replyWindowLink"
 | 
						|
      :href-link-text="replyWindowLinkText"
 | 
						|
    />
 | 
						|
    <div class="flex justify-end">
 | 
						|
      <woot-button
 | 
						|
        variant="smooth"
 | 
						|
        size="tiny"
 | 
						|
        color-scheme="secondary"
 | 
						|
        class="rounded-bl-calc rtl:rotate-180 rounded-tl-calc fixed top-[9.5rem] md:top-[6.25rem] z-10 bg-white dark:bg-slate-700 border-slate-50 dark:border-slate-600 border-solid border border-r-0 box-border"
 | 
						|
        :icon="isRightOrLeftIcon"
 | 
						|
        @click="onToggleContactPanel"
 | 
						|
      />
 | 
						|
    </div>
 | 
						|
    <ul class="conversation-panel">
 | 
						|
      <transition name="slide-up">
 | 
						|
        <li class="min-h-[4rem]">
 | 
						|
          <span v-if="shouldShowSpinner" class="spinner message" />
 | 
						|
        </li>
 | 
						|
      </transition>
 | 
						|
      <message
 | 
						|
        v-for="message in getReadMessages"
 | 
						|
        :key="message.id"
 | 
						|
        class="message--read ph-no-capture"
 | 
						|
        data-clarity-mask="True"
 | 
						|
        :data="message"
 | 
						|
        :is-a-tweet="isATweet"
 | 
						|
        :is-a-whatsapp-channel="isAWhatsAppChannel"
 | 
						|
        :is-web-widget-inbox="isAWebWidgetInbox"
 | 
						|
        :inbox-supports-reply-to="inboxSupportsReplyTo"
 | 
						|
        :in-reply-to="getInReplyToMessage(message)"
 | 
						|
      />
 | 
						|
      <li v-show="unreadMessageCount != 0" class="unread--toast">
 | 
						|
        <span>
 | 
						|
          {{ unreadMessageCount > 9 ? '9+' : unreadMessageCount }}
 | 
						|
          {{
 | 
						|
            unreadMessageCount > 1
 | 
						|
              ? $t('CONVERSATION.UNREAD_MESSAGES')
 | 
						|
              : $t('CONVERSATION.UNREAD_MESSAGE')
 | 
						|
          }}
 | 
						|
        </span>
 | 
						|
      </li>
 | 
						|
      <message
 | 
						|
        v-for="message in getUnReadMessages"
 | 
						|
        :key="message.id"
 | 
						|
        class="message--unread ph-no-capture"
 | 
						|
        data-clarity-mask="True"
 | 
						|
        :data="message"
 | 
						|
        :is-a-tweet="isATweet"
 | 
						|
        :is-a-whatsapp-channel="isAWhatsAppChannel"
 | 
						|
        :is-web-widget-inbox="isAWebWidgetInbox"
 | 
						|
        :inbox-supports-reply-to="inboxSupportsReplyTo"
 | 
						|
        :in-reply-to="getInReplyToMessage(message)"
 | 
						|
      />
 | 
						|
      <conversation-label-suggestion
 | 
						|
        v-if="shouldShowLabelSuggestions"
 | 
						|
        :suggested-labels="labelSuggestions"
 | 
						|
        :chat-labels="currentChat.labels"
 | 
						|
        :conversation-id="currentChat.id"
 | 
						|
      />
 | 
						|
    </ul>
 | 
						|
    <div
 | 
						|
      class="conversation-footer"
 | 
						|
      :class="{ 'modal-mask': isPopoutReplyBox }"
 | 
						|
    >
 | 
						|
      <div v-if="isAnyoneTyping" class="typing-indicator-wrap">
 | 
						|
        <div class="typing-indicator">
 | 
						|
          {{ typingUserNames }}
 | 
						|
          <img
 | 
						|
            class="gif"
 | 
						|
            src="~dashboard/assets/images/typing.gif"
 | 
						|
            alt="Someone is typing"
 | 
						|
          />
 | 
						|
        </div>
 | 
						|
      </div>
 | 
						|
      <reply-box
 | 
						|
        :conversation-id="currentChat.id"
 | 
						|
        :popout-reply-box.sync="isPopoutReplyBox"
 | 
						|
        @click="showPopoutReplyBox"
 | 
						|
      />
 | 
						|
    </div>
 | 
						|
  </div>
 | 
						|
</template>
 | 
						|
 | 
						|
<script>
 | 
						|
// components
 | 
						|
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
 | 
						|
import { mapGetters } from 'vuex';
 | 
						|
 | 
						|
// mixins
 | 
						|
import conversationMixin, {
 | 
						|
  filterDuplicateSourceMessages,
 | 
						|
} from '../../../mixins/conversations';
 | 
						|
import inboxMixin, { INBOX_FEATURES } from 'shared/mixins/inboxMixin';
 | 
						|
import configMixin from 'shared/mixins/configMixin';
 | 
						|
import eventListenerMixins from 'shared/mixins/eventListenerMixins';
 | 
						|
import aiMixin from 'dashboard/mixins/aiMixin';
 | 
						|
 | 
						|
// utils
 | 
						|
import { getTypingUsersText } from '../../../helper/commons';
 | 
						|
import { calculateScrollTop } from './helpers/scrollTopCalculationHelper';
 | 
						|
import { isEscape } from 'shared/helpers/KeyboardHelpers';
 | 
						|
import { LocalStorage } from 'shared/helpers/localStorage';
 | 
						|
 | 
						|
// constants
 | 
						|
import { BUS_EVENTS } from 'shared/constants/busEvents';
 | 
						|
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
 | 
						|
import { REPLY_POLICY } from 'shared/constants/links';
 | 
						|
import wootConstants from 'dashboard/constants/globals';
 | 
						|
import { LOCAL_STORAGE_KEYS } from 'dashboard/constants/localStorage';
 | 
						|
 | 
						|
export default {
 | 
						|
  components: {
 | 
						|
    Message,
 | 
						|
    ReplyBox,
 | 
						|
    Banner,
 | 
						|
    ConversationLabelSuggestion,
 | 
						|
  },
 | 
						|
  mixins: [
 | 
						|
    conversationMixin,
 | 
						|
    inboxMixin,
 | 
						|
    eventListenerMixins,
 | 
						|
    configMixin,
 | 
						|
    aiMixin,
 | 
						|
  ],
 | 
						|
  props: {
 | 
						|
    isContactPanelOpen: {
 | 
						|
      type: Boolean,
 | 
						|
      default: false,
 | 
						|
    },
 | 
						|
  },
 | 
						|
 | 
						|
  data() {
 | 
						|
    return {
 | 
						|
      isLoadingPrevious: true,
 | 
						|
      heightBeforeLoad: null,
 | 
						|
      conversationPanel: null,
 | 
						|
      hasUserScrolled: false,
 | 
						|
      isProgrammaticScroll: false,
 | 
						|
      isPopoutReplyBox: false,
 | 
						|
      messageSentSinceOpened: false,
 | 
						|
      labelSuggestions: [],
 | 
						|
    };
 | 
						|
  },
 | 
						|
 | 
						|
  computed: {
 | 
						|
    ...mapGetters({
 | 
						|
      accountId: 'getCurrentAccountId',
 | 
						|
      currentChat: 'getSelectedChat',
 | 
						|
      allConversations: 'getAllConversations',
 | 
						|
      inboxesList: 'inboxes/getInboxes',
 | 
						|
      listLoadingStatus: 'getAllMessagesLoaded',
 | 
						|
      loadingChatList: 'getChatListLoadingStatus',
 | 
						|
      appIntegrations: 'integrations/getAppIntegrations',
 | 
						|
      isFeatureEnabledonAccount: 'accounts/isFeatureEnabledonAccount',
 | 
						|
      currentAccountId: 'getCurrentAccountId',
 | 
						|
    }),
 | 
						|
    isOpen() {
 | 
						|
      return this.currentChat?.status === wootConstants.STATUS_TYPE.OPEN;
 | 
						|
    },
 | 
						|
    shouldShowLabelSuggestions() {
 | 
						|
      return (
 | 
						|
        this.isOpen &&
 | 
						|
        this.isEnterprise &&
 | 
						|
        this.isAIIntegrationEnabled &&
 | 
						|
        !this.messageSentSinceOpened
 | 
						|
      );
 | 
						|
    },
 | 
						|
    inboxId() {
 | 
						|
      return this.currentChat.inbox_id;
 | 
						|
    },
 | 
						|
    inbox() {
 | 
						|
      return this.$store.getters['inboxes/getInbox'](this.inboxId);
 | 
						|
    },
 | 
						|
    typingUsersList() {
 | 
						|
      const userList = this.$store.getters[
 | 
						|
        'conversationTypingStatus/getUserList'
 | 
						|
      ](this.currentChat.id);
 | 
						|
      return userList;
 | 
						|
    },
 | 
						|
    isAnyoneTyping() {
 | 
						|
      const userList = this.typingUsersList;
 | 
						|
      return userList.length !== 0;
 | 
						|
    },
 | 
						|
    typingUserNames() {
 | 
						|
      const userList = this.typingUsersList;
 | 
						|
 | 
						|
      if (this.isAnyoneTyping) {
 | 
						|
        const userListAsName = getTypingUsersText(userList);
 | 
						|
        return userListAsName;
 | 
						|
      }
 | 
						|
 | 
						|
      return '';
 | 
						|
    },
 | 
						|
    getMessages() {
 | 
						|
      const messages = this.currentChat.messages || [];
 | 
						|
      if (this.isAWhatsAppChannel) {
 | 
						|
        return filterDuplicateSourceMessages(messages);
 | 
						|
      }
 | 
						|
      return messages;
 | 
						|
    },
 | 
						|
    getReadMessages() {
 | 
						|
      return this.readMessages(
 | 
						|
        this.getMessages,
 | 
						|
        this.currentChat.agent_last_seen_at
 | 
						|
      );
 | 
						|
    },
 | 
						|
    getUnReadMessages() {
 | 
						|
      return this.unReadMessages(
 | 
						|
        this.getMessages,
 | 
						|
        this.currentChat.agent_last_seen_at
 | 
						|
      );
 | 
						|
    },
 | 
						|
    shouldShowSpinner() {
 | 
						|
      return (
 | 
						|
        (this.currentChat && this.currentChat.dataFetched === undefined) ||
 | 
						|
        (!this.listLoadingStatus && this.isLoadingPrevious)
 | 
						|
      );
 | 
						|
    },
 | 
						|
    conversationType() {
 | 
						|
      const { additional_attributes: additionalAttributes } = this.currentChat;
 | 
						|
      const type = additionalAttributes ? additionalAttributes.type : '';
 | 
						|
      return type || '';
 | 
						|
    },
 | 
						|
 | 
						|
    isATweet() {
 | 
						|
      return this.conversationType === 'tweet';
 | 
						|
    },
 | 
						|
    isRightOrLeftIcon() {
 | 
						|
      if (this.isContactPanelOpen) {
 | 
						|
        return 'arrow-chevron-right';
 | 
						|
      }
 | 
						|
      return 'arrow-chevron-left';
 | 
						|
    },
 | 
						|
    getLastSeenAt() {
 | 
						|
      const { contact_last_seen_at: contactLastSeenAt } = this.currentChat;
 | 
						|
      return contactLastSeenAt;
 | 
						|
    },
 | 
						|
 | 
						|
    replyWindowBannerMessage() {
 | 
						|
      if (this.isAWhatsAppChannel) {
 | 
						|
        return this.$t('CONVERSATION.TWILIO_WHATSAPP_CAN_REPLY');
 | 
						|
      }
 | 
						|
      if (this.isAPIInbox) {
 | 
						|
        const { additional_attributes: additionalAttributes = {} } = this.inbox;
 | 
						|
        if (additionalAttributes) {
 | 
						|
          const {
 | 
						|
            agent_reply_time_window_message: agentReplyTimeWindowMessage,
 | 
						|
          } = additionalAttributes;
 | 
						|
          return agentReplyTimeWindowMessage;
 | 
						|
        }
 | 
						|
        return '';
 | 
						|
      }
 | 
						|
      return this.$t('CONVERSATION.CANNOT_REPLY');
 | 
						|
    },
 | 
						|
    replyWindowLink() {
 | 
						|
      if (this.isAWhatsAppChannel) {
 | 
						|
        return REPLY_POLICY.FACEBOOK;
 | 
						|
      }
 | 
						|
      if (!this.isAPIInbox) {
 | 
						|
        return REPLY_POLICY.TWILIO_WHATSAPP;
 | 
						|
      }
 | 
						|
      return '';
 | 
						|
    },
 | 
						|
    replyWindowLinkText() {
 | 
						|
      if (this.isAWhatsAppChannel) {
 | 
						|
        return this.$t('CONVERSATION.24_HOURS_WINDOW');
 | 
						|
      }
 | 
						|
      if (!this.isAPIInbox) {
 | 
						|
        return this.$t('CONVERSATION.TWILIO_WHATSAPP_24_HOURS_WINDOW');
 | 
						|
      }
 | 
						|
      return '';
 | 
						|
    },
 | 
						|
    unreadMessageCount() {
 | 
						|
      return this.currentChat.unread_count || 0;
 | 
						|
    },
 | 
						|
    inboxSupportsReplyTo() {
 | 
						|
      return (
 | 
						|
        this.inboxHasFeature(INBOX_FEATURES.REPLY_TO_OUTGOING) &&
 | 
						|
        this.isFeatureEnabledonAccount(
 | 
						|
          this.accountId,
 | 
						|
          FEATURE_FLAGS.MESSAGE_REPLY_TO
 | 
						|
        )
 | 
						|
      );
 | 
						|
    },
 | 
						|
  },
 | 
						|
 | 
						|
  watch: {
 | 
						|
    currentChat(newChat, oldChat) {
 | 
						|
      if (newChat.id === oldChat.id) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      this.fetchAllAttachmentsFromCurrentChat();
 | 
						|
      this.fetchSuggestions();
 | 
						|
      this.messageSentSinceOpened = false;
 | 
						|
    },
 | 
						|
  },
 | 
						|
 | 
						|
  created() {
 | 
						|
    bus.$on(BUS_EVENTS.SCROLL_TO_MESSAGE, this.onScrollToMessage);
 | 
						|
    // when a new message comes in, we refetch the label suggestions
 | 
						|
    bus.$on(BUS_EVENTS.FETCH_LABEL_SUGGESTIONS, this.fetchSuggestions);
 | 
						|
    // when a message is sent we set the flag to true this hides the label suggestions,
 | 
						|
    // until the chat is changed and the flag is reset in the watch for currentChat
 | 
						|
    bus.$on(BUS_EVENTS.MESSAGE_SENT, () => {
 | 
						|
      this.messageSentSinceOpened = true;
 | 
						|
    });
 | 
						|
  },
 | 
						|
 | 
						|
  mounted() {
 | 
						|
    this.addScrollListener();
 | 
						|
    this.fetchAllAttachmentsFromCurrentChat();
 | 
						|
    this.fetchSuggestions();
 | 
						|
  },
 | 
						|
 | 
						|
  beforeDestroy() {
 | 
						|
    this.removeBusListeners();
 | 
						|
    this.removeScrollListener();
 | 
						|
  },
 | 
						|
 | 
						|
  methods: {
 | 
						|
    async fetchSuggestions() {
 | 
						|
      // start empty, this ensures that the label suggestions are not shown
 | 
						|
      this.labelSuggestions = [];
 | 
						|
 | 
						|
      if (this.isLabelSuggestionDismissed()) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      if (!this.isEnterprise) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      // method available in mixin, need to ensure that integrations are present
 | 
						|
      await this.fetchIntegrationsIfRequired();
 | 
						|
 | 
						|
      if (!this.isAIIntegrationEnabled) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      this.labelSuggestions = await this.fetchLabelSuggestions({
 | 
						|
        conversationId: this.currentChat.id,
 | 
						|
      });
 | 
						|
 | 
						|
      // once the labels are fetched, we need to scroll to bottom
 | 
						|
      // but we need to wait for the DOM to be updated
 | 
						|
      // so we use the nextTick method
 | 
						|
      this.$nextTick(() => {
 | 
						|
        // this param is added to route, telling the UI to navigate to the message
 | 
						|
        // it is triggered by the SCROLL_TO_MESSAGE method
 | 
						|
        // see setActiveChat on ConversationView.vue for more info
 | 
						|
        const { messageId } = this.$route.query;
 | 
						|
 | 
						|
        // only trigger the scroll to bottom if the user has not scrolled
 | 
						|
        // and there's no active messageId that is selected in view
 | 
						|
        if (!messageId && !this.hasUserScrolled) {
 | 
						|
          this.scrollToBottom();
 | 
						|
        }
 | 
						|
      });
 | 
						|
    },
 | 
						|
    isLabelSuggestionDismissed() {
 | 
						|
      return LocalStorage.getFlag(
 | 
						|
        LOCAL_STORAGE_KEYS.DISMISSED_LABEL_SUGGESTIONS,
 | 
						|
        this.currentAccountId,
 | 
						|
        this.currentChat.id
 | 
						|
      );
 | 
						|
    },
 | 
						|
    fetchAllAttachmentsFromCurrentChat() {
 | 
						|
      this.$store.dispatch('fetchAllAttachments', this.currentChat.id);
 | 
						|
    },
 | 
						|
    removeBusListeners() {
 | 
						|
      bus.$off(BUS_EVENTS.SCROLL_TO_MESSAGE, this.onScrollToMessage);
 | 
						|
    },
 | 
						|
    onScrollToMessage({ messageId = '' } = {}) {
 | 
						|
      this.$nextTick(() => {
 | 
						|
        const messageElement = document.getElementById('message' + messageId);
 | 
						|
        if (messageElement) {
 | 
						|
          this.isProgrammaticScroll = true;
 | 
						|
          messageElement.scrollIntoView({ behavior: 'smooth' });
 | 
						|
          this.fetchPreviousMessages();
 | 
						|
        } else {
 | 
						|
          this.scrollToBottom();
 | 
						|
        }
 | 
						|
      });
 | 
						|
      this.makeMessagesRead();
 | 
						|
    },
 | 
						|
    showPopoutReplyBox() {
 | 
						|
      this.isPopoutReplyBox = !this.isPopoutReplyBox;
 | 
						|
    },
 | 
						|
    closePopoutReplyBox() {
 | 
						|
      this.isPopoutReplyBox = false;
 | 
						|
    },
 | 
						|
    handleKeyEvents(e) {
 | 
						|
      if (isEscape(e)) {
 | 
						|
        this.closePopoutReplyBox();
 | 
						|
      }
 | 
						|
    },
 | 
						|
    addScrollListener() {
 | 
						|
      this.conversationPanel = this.$el.querySelector('.conversation-panel');
 | 
						|
      this.setScrollParams();
 | 
						|
      this.conversationPanel.addEventListener('scroll', this.handleScroll);
 | 
						|
      this.$nextTick(() => this.scrollToBottom());
 | 
						|
      this.isLoadingPrevious = false;
 | 
						|
    },
 | 
						|
    removeScrollListener() {
 | 
						|
      this.conversationPanel.removeEventListener('scroll', this.handleScroll);
 | 
						|
    },
 | 
						|
    scrollToBottom() {
 | 
						|
      this.isProgrammaticScroll = true;
 | 
						|
      let relevantMessages = [];
 | 
						|
 | 
						|
      // label suggestions are not part of the messages list
 | 
						|
      // so we need to handle them separately
 | 
						|
      let labelSuggestions =
 | 
						|
        this.conversationPanel.querySelector('.label-suggestion');
 | 
						|
 | 
						|
      // if there are unread messages, scroll to the first unread message
 | 
						|
      if (this.unreadMessageCount > 0) {
 | 
						|
        // capturing only the unread messages
 | 
						|
        relevantMessages =
 | 
						|
          this.conversationPanel.querySelectorAll('.message--unread');
 | 
						|
      } else if (labelSuggestions) {
 | 
						|
        // when scrolling to the bottom, the label suggestions is below the last message
 | 
						|
        // so we scroll there if there are no unread messages
 | 
						|
        // Unread messages always take the highest priority
 | 
						|
        relevantMessages = [labelSuggestions];
 | 
						|
      } else {
 | 
						|
        // if there are no unread messages or label suggestion, scroll to the last message
 | 
						|
        // capturing last message from the messages list
 | 
						|
        relevantMessages = Array.from(
 | 
						|
          this.conversationPanel.querySelectorAll('.message--read')
 | 
						|
        ).slice(-1);
 | 
						|
      }
 | 
						|
 | 
						|
      this.conversationPanel.scrollTop = calculateScrollTop(
 | 
						|
        this.conversationPanel.scrollHeight,
 | 
						|
        this.$el.scrollHeight,
 | 
						|
        relevantMessages
 | 
						|
      );
 | 
						|
    },
 | 
						|
    onToggleContactPanel() {
 | 
						|
      this.$emit('contact-panel-toggle');
 | 
						|
    },
 | 
						|
    setScrollParams() {
 | 
						|
      this.heightBeforeLoad = this.conversationPanel.scrollHeight;
 | 
						|
      this.scrollTopBeforeLoad = this.conversationPanel.scrollTop;
 | 
						|
    },
 | 
						|
 | 
						|
    async fetchPreviousMessages(scrollTop = 0) {
 | 
						|
      this.setScrollParams();
 | 
						|
      const shouldLoadMoreMessages =
 | 
						|
        this.currentChat.dataFetched === true &&
 | 
						|
        !this.listLoadingStatus &&
 | 
						|
        !this.isLoadingPrevious;
 | 
						|
 | 
						|
      if (
 | 
						|
        scrollTop < 100 &&
 | 
						|
        !this.isLoadingPrevious &&
 | 
						|
        shouldLoadMoreMessages
 | 
						|
      ) {
 | 
						|
        this.isLoadingPrevious = true;
 | 
						|
        try {
 | 
						|
          await this.$store.dispatch('fetchPreviousMessages', {
 | 
						|
            conversationId: this.currentChat.id,
 | 
						|
            before: this.currentChat.messages[0].id,
 | 
						|
          });
 | 
						|
          const heightDifference =
 | 
						|
            this.conversationPanel.scrollHeight - this.heightBeforeLoad;
 | 
						|
          this.conversationPanel.scrollTop =
 | 
						|
            this.scrollTopBeforeLoad + heightDifference;
 | 
						|
          this.setScrollParams();
 | 
						|
        } catch (error) {
 | 
						|
          // Ignore Error
 | 
						|
        } finally {
 | 
						|
          this.isLoadingPrevious = false;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    },
 | 
						|
 | 
						|
    handleScroll(e) {
 | 
						|
      if (this.isProgrammaticScroll) {
 | 
						|
        // Reset the flag
 | 
						|
        this.isProgrammaticScroll = false;
 | 
						|
        this.hasUserScrolled = false;
 | 
						|
      } else {
 | 
						|
        this.hasUserScrolled = true;
 | 
						|
      }
 | 
						|
      bus.$emit(BUS_EVENTS.ON_MESSAGE_LIST_SCROLL);
 | 
						|
      this.fetchPreviousMessages(e.target.scrollTop);
 | 
						|
    },
 | 
						|
 | 
						|
    makeMessagesRead() {
 | 
						|
      this.$store.dispatch('markMessagesRead', { id: this.currentChat.id });
 | 
						|
    },
 | 
						|
 | 
						|
    getInReplyToMessage(parentMessage) {
 | 
						|
      if (!parentMessage) return {};
 | 
						|
      const inReplyToMessageId = parentMessage.content_attributes?.in_reply_to;
 | 
						|
      if (!inReplyToMessageId) return {};
 | 
						|
 | 
						|
      return this.currentChat?.messages.find(message => {
 | 
						|
        if (message.id === inReplyToMessageId) {
 | 
						|
          return true;
 | 
						|
        }
 | 
						|
        return false;
 | 
						|
      });
 | 
						|
    },
 | 
						|
  },
 | 
						|
};
 | 
						|
</script>
 | 
						|
 | 
						|
<style scoped>
 | 
						|
@tailwind components;
 | 
						|
@layer components {
 | 
						|
  .rounded-bl-calc {
 | 
						|
    border-bottom-left-radius: calc(1.5rem + 1px);
 | 
						|
  }
 | 
						|
 | 
						|
  .rounded-tl-calc {
 | 
						|
    border-top-left-radius: calc(1.5rem + 1px);
 | 
						|
  }
 | 
						|
}
 | 
						|
</style>
 | 
						|
 | 
						|
<style scoped lang="scss">
 | 
						|
.modal-mask {
 | 
						|
  &::v-deep {
 | 
						|
    .ProseMirror-woot-style {
 | 
						|
      @apply max-h-[25rem];
 | 
						|
    }
 | 
						|
 | 
						|
    .reply-box {
 | 
						|
      @apply border border-solid border-slate-75 dark:border-slate-600 max-w-[75rem] w-[70%];
 | 
						|
    }
 | 
						|
 | 
						|
    .reply-box .reply-box__top {
 | 
						|
      @apply relative min-h-[27.5rem];
 | 
						|
    }
 | 
						|
 | 
						|
    .reply-box__top .input {
 | 
						|
      @apply min-h-[27.5rem];
 | 
						|
    }
 | 
						|
 | 
						|
    .emoji-dialog {
 | 
						|
      @apply absolute left-auto bottom-1;
 | 
						|
    }
 | 
						|
  }
 | 
						|
}
 | 
						|
</style>
 |