diff --git a/website/client/app.vue b/website/client/app.vue index cae0f46a12..510b69089e 100644 --- a/website/client/app.vue +++ b/website/client/app.vue @@ -138,12 +138,21 @@ export default { // @TODO split up this file, it's too big // Set up Error interceptors axios.interceptors.response.use((response) => { - if (this.user) { + if (this.user && response.data && response.data.notifications) { this.$set(this.user, 'notifications', response.data.notifications); } return response; }, (error) => { if (error.response.status >= 400) { + // Don't show errors from getting user details. These users have delete their account, + // but their chat message still exists. + let configExists = Boolean(error.response) && Boolean(error.response.config); + if (configExists && error.response.config.method === 'get' && error.response.config.url.indexOf('/api/v3/members/') !== -1) { + // @TODO: We resolve the promise because we need our caching to cache this user as tried + // Chat paging should help this, but maybe we can also find another solution.. + return Promise.resolve(error); + } + this.$store.state.notificationStore.push({ title: 'Habitica', text: error.response.data.message, diff --git a/website/client/components/chat/chatMessages.vue b/website/client/components/chat/chatMessages.vue index fdaca67f42..9396e2e809 100644 --- a/website/client/components/chat/chatMessages.vue +++ b/website/client/components/chat/chatMessages.vue @@ -5,14 +5,14 @@ copy-as-todo-modal(:copying-message='copyingMessage', :group-name='groupName', :group-id='groupId') report-flag-modal - div(v-for="(msg, index) in chat", v-if='chat && canViewFlag(msg)') + div(v-for="(msg, index) in messages", v-if='chat && canViewFlag(msg)') // @TODO: is there a different way to do these conditionals? This creates an infinite loop //.hr(v-if='displayDivider(msg)') .hr-middle(v-once) {{ msg.timestamp }} .row(v-if='user._id !== msg.uuid') div(:class='inbox ? "col-4" : "col-2"') avatar( - v-if='cachedProfileData[msg.uuid]', + v-if='cachedProfileData[msg.uuid] && !cachedProfileData[msg.uuid].rejected', :member="cachedProfileData[msg.uuid]", :avatarOnly="true", :hideClassBadge='true', @@ -89,7 +89,7 @@ | + {{ likeCount(msg) }} div(:class='inbox ? "col-4" : "col-2"') avatar( - v-if='cachedProfileData[msg.uuid]', + v-if='cachedProfileData[msg.uuid] && !cachedProfileData[msg.uuid].rejected', :member="cachedProfileData[msg.uuid]", :avatarOnly="true", :hideClassBadge='true', @@ -243,7 +243,7 @@ import axios from 'axios'; import moment from 'moment'; import cloneDeep from 'lodash/cloneDeep'; import { mapState } from 'client/libs/store'; -import throttle from 'lodash/throttle'; +import debounce from 'lodash/debounce'; import markdownDirective from 'client/directives/markdown'; import Avatar from '../avatar'; import styleHelper from 'client/mixins/styleHelper'; @@ -282,12 +282,10 @@ export default { this.loadProfileCache(); }, created () { - window.addEventListener('scroll', throttle(() => { - this.loadProfileCache(window.scrollY / 1000); - }, 1000)); + window.addEventListener('scroll', this.handleScroll); }, destroyed () { - // window.removeEventListener('scroll', this.handleScroll); + window.removeEventListener('scroll', this.handleScroll); }, data () { return { @@ -313,6 +311,7 @@ export default { cachedProfileData: {}, currentProfileLoadedCount: 0, currentProfileLoadedEnd: 10, + loading: false, }; }, filters: { @@ -326,17 +325,22 @@ export default { }, computed: { ...mapState({user: 'user.data'}), + // @TODO: We need a different lazy load mechnism. + // But honestly, adding a paging route to chat would solve this messages () { return this.chat; }, }, watch: { - messages () { - // @TODO: MAybe we should watch insert and remove? + messages (oldValue, newValue) { + if (newValue.length === oldValue.length) return; this.loadProfileCache(); }, }, methods: { + handleScroll () { + this.loadProfileCache(window.scrollY / 1000); + }, isUserMentioned (message) { let user = this.user; @@ -363,7 +367,10 @@ export default { if (!message.flagCount || message.flagCount < 2) return true; return this.user.contributor.admin; }, - async loadProfileCache (screenPosition) { + loadProfileCache: debounce(function loadProfileCache (screenPosition) { + this._loadProfileCache(screenPosition); + }, 1000), + async _loadProfileCache (screenPosition) { let promises = []; // @TODO: write an explination @@ -373,11 +380,10 @@ export default { return; } - // @TODO: Not sure we need this hash let aboutToCache = {}; this.messages.forEach(message => { let uuid = message.uuid; - if (uuid && !this.cachedProfileData[uuid] && !aboutToCache[uuid]) { + if (Boolean(uuid) && !this.cachedProfileData[uuid] && !aboutToCache[uuid]) { if (uuid === 'system' || this.currentProfileLoadedCount === this.currentProfileLoadedEnd) return; aboutToCache[uuid] = {}; promises.push(axios.get(`/api/v3/members/${uuid}`)); @@ -387,9 +393,21 @@ export default { let results = await Promise.all(promises); results.forEach(result => { + // We could not load the user. Maybe they were deleted. So, let's cache empty so we don't try again + if (!result || !result.data || result.status >= 400) { + return; + } + let userData = result.data.data; this.$set(this.cachedProfileData, userData._id, userData); }); + + // Merge in any attempts that were rejected so we don't attempt again + for (let uuid in aboutToCache) { + if (!this.cachedProfileData[uuid]) { + this.$set(this.cachedProfileData, uuid, {rejected: true}); + } + } }, displayDivider (message) { if (this.currentDayDividerDisplay !== moment(message.timestamp).day()) { diff --git a/website/client/components/groups/group.vue b/website/client/components/groups/group.vue index c02fd1f518..0744bbb1a5 100644 --- a/website/client/components/groups/group.vue +++ b/website/client/components/groups/group.vue @@ -431,6 +431,7 @@