55 :variables =" {
66 id
77 }"
8+ @result =" onResult"
89 >
910 <template slot-scope="{ result: { data, loading } }">
10- <div v-if =" loading" class =" loading" >Loading...</div >
11+ <div v-if =" !data && loading" class =" loading" >Loading...</div >
1112
1213 <div v-else-if =" data" >
14+ <!-- Websockets -->
15+ <ApolloSubscribeToMore
16+ :document =" require('../graphql/messageChanged.gql')"
17+ :variables =" {
18+ channelId: id,
19+ }"
20+ :updateQuery =" onMessageChanged"
21+ />
22+
1323 <div class =" wrapper" >
1424 <div class =" header" >
1525 <div class =" id" >#{{ data.channel.id }}</div >
1626 <div class =" name" >{{ data.channel.name }}</div >
1727 </div >
18- <div class =" body" >
1928
29+ <div ref =" body" class =" body" >
30+ <MessageItem
31+ v-for =" message in data.channel.messages"
32+ :key =" message.id"
33+ :message =" message"
34+ />
2035 </div >
36+
2137 <div class =" footer" >
2238 <MessageForm :channel-id =" id" />
2339 </div >
2945</template >
3046
3147<script >
48+ import MessageItem from ' ./MessageItem.vue'
3249import MessageForm from ' ./MessageForm.vue'
3350
3451export default {
3552 name: ' ChannelView' ,
3653
3754 components: {
55+ MessageItem,
3856 MessageForm,
3957 },
4058
@@ -44,6 +62,63 @@ export default {
4462 required: true ,
4563 },
4664 },
65+
66+ watch: {
67+ id: {
68+ handler () {
69+ this .$_init = false
70+ },
71+ immediate: true ,
72+ },
73+ },
74+
75+ methods: {
76+ onMessageChanged (previousResult , { subscriptionData }) {
77+ const { type , message } = subscriptionData .data .messageChanged
78+
79+ // No list change
80+ if (type === ' updated' ) return previousResult
81+
82+ const messages = previousResult .channel .messages .slice ()
83+ // Add or remove item
84+ if (type === ' added' ) {
85+ messages .push (message)
86+ } else if (type === ' removed' ) {
87+ const index = messages .findIndex (m => m .id === message .id )
88+ if (index !== - 1 ) messages .splice (index, 1 )
89+ }
90+
91+ // New query result
92+ return {
93+ channel: {
94+ ... previousResult .channel ,
95+ messages,
96+ },
97+ }
98+ },
99+
100+ async scrollToBottom (force = false ) {
101+ let el = this .$refs .body
102+
103+ // No body element yet
104+ if (! el) {
105+ setTimeout (() => this .scrollToBottom (force), 100 )
106+ return
107+ }
108+ // User is scrolling up => no auto scroll
109+ if (! force && el .scrollTop + el .clientHeight < el .scrollHeight - 100 ) return
110+
111+ // Scroll to bottom
112+ await this .$nextTick ()
113+ el .scrollTop = el .scrollHeight
114+ },
115+
116+ onResult () {
117+ // The first time we load a channel, we force scroll to bottom
118+ this .scrollToBottom (! this .$_init )
119+ this .$_init = true
120+ },
121+ },
47122}
48123 </script >
49124
@@ -57,7 +132,7 @@ export default {
57132 grid-template-rows auto 1 fr auto
58133
59134.header
60- padding 8 px
135+ padding 12 px
61136 border-bottom $border
62137
63138.id
@@ -67,6 +142,10 @@ export default {
67142.name
68143 color #5 5 5
69144
145+ .body
146+ overflow-x hidden
147+ overflow-y auto
148+
70149.footer
71150 border-top $border
72151 </style >
0 commit comments