diff --git a/app/api/server/lib/users.js b/app/api/server/lib/users.js index 82194832ceb..4d7226a608d 100644 --- a/app/api/server/lib/users.js +++ b/app/api/server/lib/users.js @@ -14,6 +14,7 @@ export async function findUsersToAutocomplete({ uid, selector }) { name: 1, username: 1, status: 1, + avatarETag: 1, }, sort: { username: 1, diff --git a/app/api/server/v1/users.js b/app/api/server/v1/users.js index c252b743e84..4eebd8bc7bb 100644 --- a/app/api/server/v1/users.js +++ b/app/api/server/v1/users.js @@ -716,6 +716,7 @@ API.v1.addRoute('users.presence', { authRequired: true }, { status: 1, utcOffset: 1, statusText: 1, + avatarETag: 1, }, }; diff --git a/app/lazy-load/client/index.js b/app/lazy-load/client/index.js index 61a0ece9c73..7bb31b6e897 100644 --- a/app/lazy-load/client/index.js +++ b/app/lazy-load/client/index.js @@ -9,14 +9,16 @@ const loadImage = (el) => { map.delete(el); if (!instance) { - return instance.loaded.set(true); + return; } + const img = new Image(); const src = el.getAttribute('data-src'); img.onload = () => { el.className = el.className.replace('lazy-img', ''); el.src = src; el.removeAttribute('data-src'); + instance.loaded.set(true); }; img.src = src; }; diff --git a/app/lazy-load/client/lazyloadImage.js b/app/lazy-load/client/lazyloadImage.js index c711f73ebb9..9d2151498b0 100644 --- a/app/lazy-load/client/lazyloadImage.js +++ b/app/lazy-load/client/lazyloadImage.js @@ -4,7 +4,7 @@ import { Template } from 'meteor/templating'; import './lazyloadImage.html'; import { addImage } from '.'; -const emptyImageEncoded = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8+/u3PQAJJAM0dIyWdgAAAABJRU5ErkJggg=='; +const emptyImageEncoded = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8+/u3PQAJJAM0dIyWdgAAAABJRU5ErkJggg=='; const imgsrcs = new Set(); @@ -15,12 +15,17 @@ Template.lazyloadImage.helpers({ }, srcUrl() { + if (Template.instance().loaded.get()) { + return; + } return this.src; }, lazySrcUrl() { const { preview, placeholder, src } = this; - if (Template.instance().loaded.get() || (!preview && !placeholder) || imgsrcs.has(src)) { + const { loaded } = Template.instance(); + + if (loaded.get() || (!preview && !placeholder) || imgsrcs.has(src)) { return src; } diff --git a/app/ldap/server/sync.js b/app/ldap/server/sync.js index 5e290d9bb8e..a2e6a24eb9a 100644 --- a/app/ldap/server/sync.js +++ b/app/ldap/server/sync.js @@ -409,10 +409,10 @@ export function syncUserData(user, ldapUser, ldap) { }; Meteor.runAsUser(user._id, () => { - fileStore.insert(file, rs, () => { + fileStore.insert(file, rs, (err, result) => { Meteor.setTimeout(function() { - Users.setAvatarOrigin(user._id, 'ldap'); - Notifications.notifyLogged('updateAvatar', { username: user.username }); + Users.setAvatarData(user._id, 'ldap', result.etag); + Notifications.notifyLogged('updateAvatar', { username: user.username, etag: result.etag }); }, 500); }); }); diff --git a/app/lib/lib/roomTypes/direct.js b/app/lib/lib/roomTypes/direct.js index a8b7bc73d18..e54022b5f2e 100644 --- a/app/lib/lib/roomTypes/direct.js +++ b/app/lib/lib/roomTypes/direct.js @@ -190,6 +190,11 @@ export class DirectMessageRoomType extends RoomTypeConfig { return ''; } + // if coming from sidenav search + if (roomData.name && roomData.avatarETag) { + return getUserAvatarURL(roomData.name, roomData.avatarETag); + } + if (this.isGroupChat(roomData)) { return getAvatarURL({ username: roomData.uids.length + roomData.usernames.join() }); } @@ -197,7 +202,8 @@ export class DirectMessageRoomType extends RoomTypeConfig { const sub = subData || Subscriptions.findOne({ rid: roomData._id }, { fields: { name: 1 } }); if (sub && sub.name) { - return getUserAvatarURL(sub.name); + const user = Meteor.users.findOne({ username: sub.name }, { fields: { username: 1, avatarETag: 1 } }); + return getUserAvatarURL(user?.username || sub.name, user?.avatarETag); } if (roomData) { diff --git a/app/lib/lib/roomTypes/public.js b/app/lib/lib/roomTypes/public.js index 70b2dc3fd41..e7b935805ce 100644 --- a/app/lib/lib/roomTypes/public.js +++ b/app/lib/lib/roomTypes/public.js @@ -4,7 +4,7 @@ import { openRoom } from '../../../ui-utils'; import { ChatRoom, ChatSubscription } from '../../../models'; import { settings } from '../../../settings'; import { hasAtLeastOnePermission } from '../../../authorization'; -import { getUserPreference, RoomTypeConfig, RoomTypeRouteConfig, RoomSettingsEnum, UiTextContext, RoomMemberActions } from '../../../utils'; +import { getUserPreference, RoomTypeConfig, RoomTypeRouteConfig, RoomSettingsEnum, UiTextContext, RoomMemberActions, roomTypes } from '../../../utils'; import { getAvatarURL } from '../../../utils/lib/getAvatarURL'; export class PublicRoomRoute extends RoomTypeRouteConfig { @@ -136,6 +136,17 @@ export class PublicRoomType extends RoomTypeConfig { getAvatarPath(roomData) { // TODO: change to always get avatar from _id when rooms have avatars + // if room is not a discussion, returns the avatar for its name + if (!roomData.prid) { + return getAvatarURL({ username: `@${ this.roomName(roomData) }` }); + } + + // if discussion's parent room is known, get his avatar + const proom = ChatRoom.findOne({ _id: roomData.prid }, { reactive: false }); + if (proom) { + return roomTypes.getConfig(proom.t).getAvatarPath(proom); + } + return getAvatarURL({ username: `@${ this.roomName(roomData) }` }); } diff --git a/app/lib/server/functions/getFullUserData.js b/app/lib/server/functions/getFullUserData.js index cb75e4fa37e..571977614f5 100644 --- a/app/lib/server/functions/getFullUserData.js +++ b/app/lib/server/functions/getFullUserData.js @@ -16,6 +16,7 @@ const defaultFields = { active: 1, reason: 1, statusText: 1, + avatarETag: 1, }; const fullFields = { diff --git a/app/lib/server/functions/setUserAvatar.js b/app/lib/server/functions/setUserAvatar.js index 9cab632e239..ae6c54fe1ef 100644 --- a/app/lib/server/functions/setUserAvatar.js +++ b/app/lib/server/functions/setUserAvatar.js @@ -11,7 +11,7 @@ export const setUserAvatar = function(user, dataURI, contentType, service) { let image; if (service === 'initials') { - return Users.setAvatarOrigin(user._id, service); + return Users.setAvatarData(user._id, service, null); } if (service === 'url') { let result = null; @@ -61,10 +61,10 @@ export const setUserAvatar = function(user, dataURI, contentType, service) { size: buffer.length, }; - fileStore.insert(file, buffer, () => { + fileStore.insert(file, buffer, (err, result) => { Meteor.setTimeout(function() { - Users.setAvatarOrigin(user._id, service); - Notifications.notifyLogged('updateAvatar', { username: user.username }); + Users.setAvatarData(user._id, service, result.etag); + Notifications.notifyLogged('updateAvatar', { username: user.username, etag: result.etag }); }, 500); }); }; diff --git a/app/models/server/models/Users.js b/app/models/server/models/Users.js index d5ef36e75ef..6594098a0c3 100644 --- a/app/models/server/models/Users.js +++ b/app/models/server/models/Users.js @@ -1002,20 +1002,22 @@ export class Users extends Base { return this.update(_id, update); } - setAvatarOrigin(_id, origin) { + setAvatarData(_id, origin, etag) { const update = { $set: { avatarOrigin: origin, + avatarETag: etag, }, }; return this.update(_id, update); } - unsetAvatarOrigin(_id) { + unsetAvatarData(_id) { const update = { $unset: { avatarOrigin: 1, + avatarETag: 1, }, }; diff --git a/app/ui-account/client/avatar/avatar.js b/app/ui-account/client/avatar/avatar.js index 6ea8531bde2..2035df824ff 100644 --- a/app/ui-account/client/avatar/avatar.js +++ b/app/ui-account/client/avatar/avatar.js @@ -1,18 +1,24 @@ import { Meteor } from 'meteor/meteor'; -import { Session } from 'meteor/session'; import { Template } from 'meteor/templating'; import { getUserAvatarURL } from '../../../utils/lib/getUserAvatarURL'; const getUsername = ({ userId, username }) => { + const query = {}; if (username) { - return username; + query.username = username; } if (userId) { - const user = Meteor.users.findOne(userId, { fields: { username: 1 } }); - return user && user.username; + query._id = userId; + } + + const user = Meteor.users.findOne(query, { fields: { username: 1, avatarETag: 1 } }); + if (!user) { + return {}; } + + return user; }; Template.avatar.helpers({ @@ -22,21 +28,23 @@ Template.avatar.helpers({ return url; } - let username = getUsername(this); - if (!username) { - return; + if (this.roomIcon && this.username) { + return getUserAvatarURL(`@${ this.username }`); } - Session.get(`avatar_random_${ username }`); - - if (this.roomIcon) { - username = `@${ username }`; + const { username, avatarETag } = getUsername(this); + if (!username) { + if (this.username) { + return getUserAvatarURL(this.username); + } + return; } - return getUserAvatarURL(username); + return getUserAvatarURL(username, avatarETag); }, alt() { - return getUsername(this); + const { username } = getUsername(this); + return username; }, }); diff --git a/app/ui-flextab/client/tabs/membersList.html b/app/ui-flextab/client/tabs/membersList.html index 69c75e11214..9eead962def 100644 --- a/app/ui-flextab/client/tabs/membersList.html +++ b/app/ui-flextab/client/tabs/membersList.html @@ -34,7 +34,7 @@