logo

pleroma-fe

My custom branche(s) on git.pleroma.social/pleroma/pleroma-fe

direct_conversation_view.js (13488B)


      1 import { throttle, xor } from 'lodash'
      2 import DirectConversationStatus from '../direct_conversation_status/direct_conversation_status.vue'
      3 import DirectConversationAvatar from '../direct_conversation_avatar/direct_conversation_avatar.vue'
      4 import PostStatusForm from '../post_status_form/post_status_form.vue'
      5 import DirectConversationTitle from '../direct_conversation_title/direct_conversation_title.vue'
      6 import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator'
      7 import DirectConversationInfoPage from '../direct_conversation_info_page/direct_conversation_info_page.vue'
      8 import directConversationService from '../../services/direct_conversation_service/direct_conversation_service.js'
      9 
     10 const conversation = {
     11   props: [
     12     'isInfo',
     13     'users'
     14   ],
     15   components: {
     16     DirectConversationStatus,
     17     DirectConversationTitle,
     18     DirectConversationAvatar,
     19     PostStatusForm,
     20     DirectConversationInfoPage
     21   },
     22   data () {
     23     return {
     24       loadingStatuses: true,
     25       loadingConversation: false,
     26       editedStatusId: undefined,
     27       chatParticipants: [],
     28       fetcher: undefined,
     29       jumpToBottomButtonVisible: false,
     30       mobileLayout: this.$store.state.interface.mobileLayout,
     31       conversationId: this.$route.params.id !== 'new' && this.$route.params.id,
     32       hoveredSequenceId: undefined
     33     }
     34   },
     35   created () {
     36     this.startFetching()
     37     window.addEventListener('resize', this.handleLayoutChange)
     38   },
     39   mounted () {
     40     this.$nextTick(() => {
     41       let scrollable = this.$refs.scrollable
     42       if (scrollable) {
     43         window.addEventListener('scroll', this.handleScroll)
     44       }
     45       this.updateSize()
     46     })
     47     if (this.isMobileLayout) {
     48       this.setMobileChatLayout()
     49     }
     50 
     51     if (typeof document.hidden !== 'undefined') {
     52       document.addEventListener('visibilitychange', this.handleVisibilityChange, false)
     53       this.$store.commit('setDirectConversationFocused', !document.hidden)
     54     }
     55   },
     56   destroyed () {
     57     window.removeEventListener('scroll', this.handleScroll)
     58     window.removeEventListener('resize', this.handleLayoutChange)
     59     this.unsetMobileChatLayout()
     60     if (typeof document.hidden !== 'undefined') document.removeEventListener('visibilitychange', this.handleVisibilityChange, false)
     61     this.$store.dispatch('clearCurrentDirectConversation')
     62   },
     63   computed: {
     64     formattedStatuses () {
     65       return directConversationService.getView(this.$store.state.directConversations.currentDirectConversation)
     66     },
     67     isMobileLayout () {
     68       return this.$store.state.interface.mobileLayout
     69     },
     70     currentUser () {
     71       return this.$store.state.users.currentUser
     72     },
     73     newMessagesCount () {
     74       return this.$store.state.directConversations.currentDirectConversation.newMessagesCount
     75     },
     76     isNewChat () {
     77       return (this.$route.params.id === 'new' || !this.$route.params.id) && this.queryUsers.length > 0
     78     },
     79     queryUsers () {
     80       let users = this.$route.query.users
     81       if (Array.isArray(users)) {
     82         return users
     83       } else if (users) {
     84         return [users]
     85       }
     86       return []
     87     },
     88     formPlaceholder () {
     89       const names = this.participantNames
     90       return this.$tc('direct_conversations.form_placeholder', names.length, [names.join(', ')])
     91     },
     92     participantNames () {
     93       return this.chatParticipants.filter(recipient => recipient.id !== this.currentUser.id).map(r => r.screen_name)
     94     },
     95     formScopeNotice () {
     96       const names = this.participantNames
     97       return names.length > 1 && this.$t('direct_conversations.scope_notice', [names.join(', ')])
     98     }
     99   },
    100   watch: {
    101     formattedStatuses () {
    102       let bottomedOut = this.bottomedOut(50)
    103       this.$nextTick(() => {
    104         if (bottomedOut) {
    105           this.scrollDown({ forceRead: true })
    106         }
    107       })
    108     },
    109     isInfo () {
    110       this.$nextTick(() => {
    111         this.updateSize()
    112         if (!this.isInfo) {
    113           this.scrollDown()
    114         }
    115       })
    116     }
    117   },
    118   methods: {
    119     onStatusHover ({ state, sequenceId }) {
    120       this.hoveredSequenceId = state ? sequenceId : undefined
    121     },
    122     onPosted (data) {
    123       if (!this.conversationId) {
    124         this.conversationId = data.direct_conversation_id
    125         this.doStartFetching()
    126       }
    127       this.$store.dispatch('addToCurrentDirectConversationStatuses', { statuses: [data] }).then(() => {
    128         this.updateSize()
    129         this.scrollDown({ forceRead: true })
    130       })
    131     },
    132     onScopeNoticeDismissed () {
    133       this.$nextTick(() => {
    134         this.updateSize()
    135       })
    136     },
    137     onFilesDropped () {
    138       this.$nextTick(() => {
    139         this.updateSize()
    140       })
    141     },
    142     handleVisibilityChange () {
    143       this.$store.commit('setDirectConversationFocused', !document.hidden)
    144     },
    145     onToggleActions (editedStatusId) {
    146       if (this.editedStatusId === editedStatusId) {
    147         this.editedStatusId = undefined
    148       } else {
    149         this.editedStatusId = editedStatusId
    150       }
    151     },
    152     handleLayoutChange () {
    153       this.updateSize()
    154       let mobileLayout = this.isMobileLayout
    155       if (this.mobileLayout !== mobileLayout) {
    156         if (this.mobileLayout === false && mobileLayout === true) {
    157           this.setMobileChatLayout()
    158         }
    159         if (this.mobileLayout === true && mobileLayout === false) {
    160           this.unsetMobileChatLayout()
    161         }
    162         this.mobileLayout = this.isMobileLayout
    163         this.$nextTick(() => {
    164           this.updateSize()
    165           this.scrollDown()
    166         })
    167       }
    168     },
    169     setMobileChatLayout () {
    170       // This is a hacky way to adjust the global layout to the mobile chat (without modifying the rest of the app).
    171       // This layout prevents empty spaces from being visible at the bottom
    172       // of the chat on iOS Safari (`safe-area-inset`) when
    173       // - the on-screen keyboard appears and the user starts typing
    174       // - the user selects the text inside the input area
    175       // - the user selects and deletes the text that is multiple lines long
    176       // TODO: unify the chat layout with the global layout.
    177 
    178       let html = document.querySelector('html')
    179       if (html) {
    180         html.style.overflow = 'hidden'
    181         html.style.height = '100%'
    182       }
    183 
    184       let body = document.querySelector('body')
    185       if (body) {
    186         body.style.height = '100%'
    187         body.style.overscrollBehavior = 'none'
    188       }
    189 
    190       let app = document.getElementById('app')
    191       if (app) {
    192         app.style.height = '100%'
    193         app.style.overflow = 'hidden'
    194         app.style.minHeight = 'auto'
    195       }
    196 
    197       let appBgWrapper = window.document.getElementById('app_bg_wrapper')
    198       if (appBgWrapper) {
    199         appBgWrapper.style.overflow = 'hidden'
    200       }
    201 
    202       let main = document.getElementsByClassName('main')[0]
    203       if (main) {
    204         main.style.overflow = 'hidden'
    205         main.style.height = '100%'
    206       }
    207 
    208       let content = document.getElementById('content')
    209       if (content) {
    210         content.style.paddingTop = '0'
    211         content.style.height = '100%'
    212         content.style.overflow = 'visible'
    213       }
    214 
    215       this.$nextTick(() => {
    216         this.updateSize()
    217       })
    218     },
    219     unsetMobileChatLayout () {
    220       let html = document.querySelector('html')
    221       if (html) {
    222         html.style.overflow = 'visible'
    223         html.style.height = 'unset'
    224       }
    225 
    226       let body = document.querySelector('body')
    227       if (body) {
    228         body.style.height = 'unset'
    229         body.style.overscrollBehavior = 'unset'
    230       }
    231 
    232       let app = document.getElementById('app')
    233       if (app) {
    234         app.style.height = '100%'
    235         app.style.overflow = 'visible'
    236         app.style.minHeight = '100vh'
    237       }
    238 
    239       let appBgWrapper = document.getElementById('app_bg_wrapper')
    240       if (appBgWrapper) {
    241         appBgWrapper.style.overflow = 'visible'
    242       }
    243 
    244       let main = document.getElementsByClassName('main')[0]
    245       if (main) {
    246         main.style.overflow = 'visible'
    247         main.style.height = 'unset'
    248       }
    249 
    250       let content = document.getElementById('content')
    251       if (content) {
    252         content.style.paddingTop = '60px'
    253         content.style.height = 'unset'
    254         content.style.overflow = 'unset'
    255       }
    256     },
    257     handleResize (newHeight) {
    258       this.updateSize(newHeight)
    259     },
    260     updateSize (newHeight, _diff) {
    261       let h = this.$refs.header
    262       let s = this.$refs.scrollable
    263       let f = this.$refs.footer
    264       if (h && s && f) {
    265         let height = 0
    266         if (this.isMobileLayout) {
    267           height = parseFloat(getComputedStyle(window.document.body, null).height.replace('px', ''))
    268           let newHeight = (height - h.clientHeight - f.clientHeight)
    269           s.style.height = newHeight + 'px'
    270         } else {
    271           height = parseFloat(getComputedStyle(this.$refs.inner, null).height.replace('px', ''))
    272           let newHeight = (height - h.clientHeight - f.clientHeight)
    273           s.style.height = newHeight + 'px'
    274         }
    275       }
    276     },
    277     scrollDown (options = {}) {
    278       let { behavior = 'auto', forceRead = false } = options
    279       let container = this.$refs.scrollable
    280       let scrollable = this.$refs.scrollable
    281       this.doScrollDown(scrollable, container, behavior)
    282       if (forceRead || this.newMessagesCount > 0) {
    283         this.readConversation()
    284       }
    285     },
    286     doScrollDown (scrollable, container, behavior) {
    287       if (!container) { return }
    288       setTimeout(() => {
    289         scrollable.scrollTo({ top: container.scrollHeight, left: 0, behavior })
    290       }, 100)
    291     },
    292     bottomedOut (offset) {
    293       let bottomedOut = false
    294 
    295       if (this.$refs.scrollable) {
    296         let scrollHeight = this.$refs.scrollable.scrollTop + (offset || 0)
    297         let totalHeight = this.$refs.scrollable.scrollHeight - this.$refs.scrollable.offsetHeight
    298         bottomedOut = totalHeight <= scrollHeight
    299       }
    300 
    301       return bottomedOut
    302     },
    303     handleScroll: throttle(function () {
    304       if (this.bottomedOut(150)) {
    305         this.jumpToBottomButtonVisible = false
    306         let newMessagesCount = this.$store.state.directConversations.currentDirectConversation.newMessagesCount
    307         if (newMessagesCount > 0) {
    308           this.readConversation()
    309         } else {
    310           this.$store.dispatch('resetDirectConversationNewMessageCount')
    311         }
    312       } else {
    313         this.jumpToBottomButtonVisible = true
    314       }
    315     }, 100),
    316     goBack () {
    317       this.$router.go(-1)
    318     },
    319     fetchConversation (isFirstFetch, conversationId) {
    320       this.$store.state.api.backendInteractor.directConversationTimeline({ id: conversationId })
    321         .then((statuses) => {
    322           let bottomedOut = this.bottomedOut()
    323           this.$store.dispatch('addToCurrentDirectConversationStatuses', { statuses, check: true, isFirstFetch }).then(() => {
    324             if (isFirstFetch) {
    325               setTimeout(() => {
    326                 this.updateSize()
    327                 this.scrollDown({ forceRead: true })
    328               }, 200)
    329             } else if (bottomedOut) {
    330               this.scrollDown()
    331             }
    332             setTimeout(() => {
    333               this.loadingStatuses = false
    334             }, 1000)
    335           })
    336         })
    337     },
    338     fetchConversationInfo () {
    339       this.$store.state.api.backendInteractor.directConversation({ id: this.conversationId })
    340         .then(resp => {
    341           if (resp.accounts.length > 0) {
    342             this.chatParticipants = resp.accounts
    343           } else {
    344             this.chatParticipants = [this.currentUser]
    345           }
    346         })
    347     },
    348     readConversation () {
    349       if (!this.conversationId) { return }
    350       this.$store.dispatch('readDirectConversation', { id: this.conversationId })
    351     },
    352     async resolveUsers () {
    353       let userIds = this.queryUsers
    354       let users = userIds.map(id => {
    355         let user = this.$store.getters.findUser(id)
    356         if (user) {
    357           return Promise.resolve(user)
    358         } else {
    359           return this.$store.state.api.backendInteractor.fetchUser({ id })
    360         }
    361       })
    362 
    363       let result = await Promise.all(users)
    364       return result.filter(u => u)
    365     },
    366     startFetching () {
    367       if (this.isNewChat) {
    368         this.resolveUsers().then(users => {
    369           this.chatParticipants = users
    370           let recipients = users.map(u => u.id)
    371           this.loadingConversation = true
    372           this.$store.state.api.backendInteractor.directConversations({ recipients, limit: 1 }).then(resp => {
    373             this.loadingConversation = false
    374             let conversation = resp.directConversations[0]
    375             let validConversation = conversation && xor(conversation.accounts.map(a => a.id), recipients).length === 0
    376             if (validConversation) {
    377               this.conversationId = conversation.id
    378               this.doStartFetching()
    379             }
    380           })
    381         })
    382       } else {
    383         this.doStartFetching()
    384       }
    385     },
    386     doStartFetching () {
    387       let conversationId = parseInt(this.conversationId, 10)
    388       this.$store.dispatch('startFetchingCurrentDirectConversation', {
    389         conversationId,
    390         fetcher: () => setInterval(() => this.fetchConversation(false, conversationId), 5000)
    391       })
    392       this.fetchConversationInfo()
    393       this.fetchConversation(true, conversationId)
    394     },
    395     userProfileLink (user) {
    396       return this.generateUserProfileLink(user.id, user.screen_name)
    397     },
    398     generateUserProfileLink (id, name) {
    399       return generateProfileLink(id, name, this.$store.state.instance.restrictedNicknames)
    400     }
    401   }
    402 }
    403 
    404 export default conversation