diff --git a/src/components/avatar.ts b/src/components/avatar.ts index c94509ec..6a379bb9 100644 --- a/src/components/avatar.ts +++ b/src/components/avatar.ts @@ -25,7 +25,11 @@ const onAvatarUpdate = (peerId: number) => { }; rootScope.addEventListener('avatar_update', onAvatarUpdate); -rootScope.addEventListener('peer_title_edit', onAvatarUpdate); +rootScope.addEventListener('peer_title_edit', (peerId) => { + if(!appAvatarsManager.isAvatarCached(peerId)) { + onAvatarUpdate(peerId); + } +}); export async function openAvatarViewer(target: HTMLElement, peerId: number, middleware: () => boolean, message?: any, prevTargets?: {element: HTMLElement, item: string | Message.messageService}[], nextTargets?: typeof prevTargets) { let photo = await appProfileManager.getFullPhoto(peerId); diff --git a/src/components/chat/autocompletePeerHelper.ts b/src/components/chat/autocompletePeerHelper.ts index 15a9ce19..44f55b30 100644 --- a/src/components/chat/autocompletePeerHelper.ts +++ b/src/components/chat/autocompletePeerHelper.ts @@ -83,7 +83,7 @@ export default class AutocompletePeerHelper extends AutocompleteHelper { div.dataset.peerId = '' + options.peerId; const avatar = new AvatarElement(); - avatar.classList.add('avatar-30'); + avatar.classList.add('avatar-30', options.className + '-avatar'); avatar.setAttribute('dialog', '0'); avatar.setAttribute('peer', '' + options.peerId); diff --git a/src/components/sidebarLeft/tabs/contacts.ts b/src/components/sidebarLeft/tabs/contacts.ts index b2fb2d0e..169932aa 100644 --- a/src/components/sidebarLeft/tabs/contacts.ts +++ b/src/components/sidebarLeft/tabs/contacts.ts @@ -14,22 +14,22 @@ import windowSize from "../../../helpers/windowSize"; import ButtonCorner from "../../buttonCorner"; import { attachClickEvent } from "../../../helpers/dom/clickEvent"; import PopupCreateContact from "../../popups/createContact"; +import SortedUserList from "../../sortedUserList"; +import { getMiddleware } from "../../../helpers/middleware"; +import replaceContent from "../../../helpers/dom/replaceContent"; +import rootScope from "../../../lib/rootScope"; // TODO: поиск по людям глобальный, если не нашло в контактах никого export default class AppContactsTab extends SliderSuperTab { - private list: HTMLUListElement; - private promise: Promise; - private inputSearch: InputSearch; - private alive = true; + private middleware: ReturnType; + private sortedUserList: SortedUserList; - init() { + protected init() { this.container.id = 'contacts-container'; - this.list = appDialogsManager.createChatList(/* {avatarSize: 48, handheldsSize: 66} */); - this.list.id = 'contacts'; - this.list.classList.add('contacts-container'); + // this.list = appDialogsManager.createChatList(/* {avatarSize: 48, handheldsSize: 66} */); const btnAdd = ButtonCorner({icon: 'add', className: 'is-visible'}); this.content.append(btnAdd); @@ -38,31 +38,43 @@ export default class AppContactsTab extends SliderSuperTab { new PopupCreateContact(); }, {listenerSetter: this.listenerSetter}); - appDialogsManager.setListClickListener(this.list, () => { - this.close(); - }, undefined, true); - this.inputSearch = new InputSearch('Search', (value) => { - this.list.innerHTML = ''; this.openContacts(value); }); + this.listenerSetter.add(rootScope)('contacts_update', (userId) => { + const isContact = appUsersManager.isContact(userId); + if(isContact) this.sortedUserList.add(userId); + else this.sortedUserList.delete(userId); + }); + this.title.replaceWith(this.inputSearch.container); - this.scrollable.append(this.list); + this.middleware = getMiddleware(); // preload contacts // appUsersManager.getContacts(); } - onClose() { - this.alive = false; + protected createList() { + const sortedUserList = new SortedUserList(); + const list = sortedUserList.list; + list.id = 'contacts'; + list.classList.add('contacts-container'); + appDialogsManager.setListClickListener(list, () => { + this.close(); + }, undefined, true); + return sortedUserList; + } + + protected onClose() { + this.middleware.clean(); /* // need to clear, and left 1 page for smooth slide let pageCount = appPhotosManager.windowH / 72 * 1.25 | 0; (Array.from(this.list.children) as HTMLElement[]).slice(pageCount).forEach(el => el.remove()); */ } - onOpenAfterTimeout() { + protected onOpenAfterTimeout() { if(isMobile || !canFocus(true)) return; this.inputSearch.input.focus(); } @@ -73,36 +85,29 @@ export default class AppContactsTab extends SliderSuperTab { this.init = null; } - if(this.promise) return this.promise; + this.middleware.clean(); + const middleware = this.middleware.get(); this.scrollable.onScrolledBottom = null; + this.scrollable.container.textContent = ''; - this.promise = appUsersManager.getContacts(query, undefined, 'online').then(contacts => { - this.promise = null; - - if(!this.alive) { - //console.warn('user closed contacts before it\'s loaded'); + appUsersManager.getContacts(query, undefined, 'online').then(contacts => { + if(!middleware()) { return; } + const sortedUserList = this.sortedUserList = this.createList(); + let renderPage = () => { const pageCount = windowSize.windowH / 72 * 1.25 | 0; const arr = contacts.splice(0, pageCount); // надо splice! arr.forEach((peerId) => { - const {dom} = appDialogsManager.addDialogNew({ - dialog: peerId, - container: this.list, - drawStatus: false, - avatarSize: 48, - autonomous: true - }); - - const status = appUsersManager.getUserStatusString(peerId); - dom.lastMessageSpan.append(status); + sortedUserList.add(peerId); }); if(!contacts.length) { renderPage = undefined; + this.scrollable.onScrolledBottom = null; } }; @@ -114,6 +119,8 @@ export default class AppContactsTab extends SliderSuperTab { this.scrollable.onScrolledBottom = null; } }; + + replaceContent(this.scrollable.container, sortedUserList.list); }); } diff --git a/src/components/sidebarRight/tabs/sharedMedia.ts b/src/components/sidebarRight/tabs/sharedMedia.ts index c8cfb00a..60d915ee 100644 --- a/src/components/sidebarRight/tabs/sharedMedia.ts +++ b/src/components/sidebarRight/tabs/sharedMedia.ts @@ -591,9 +591,7 @@ class PeerProfile { } }); - rootScope.addEventListener('peer_typings', (e) => { - const {peerId} = e; - + rootScope.addEventListener('peer_typings', ({peerId}) => { if(this.peerId === peerId) { this.setPeerStatus(); } @@ -605,14 +603,25 @@ class PeerProfile { } }); - rootScope.addEventListener('user_update', (e) => { - const userId = e; - + rootScope.addEventListener('user_update', (userId) => { if(this.peerId === userId) { this.setPeerStatus(); } }); + rootScope.addEventListener('contacts_update', (userId) => { + if(this.peerId === userId) { + const user = appUsersManager.getUser(userId); + if(!user.pFlags.self) { + if(user.phone) { + setText(appUsersManager.formatUserPhone(user.phone), this.phone); + } else { + this.phone.container.style.display = 'none'; + } + } + } + }); + this.setPeerStatusInterval = window.setInterval(this.setPeerStatus, 60e3); } @@ -692,7 +701,7 @@ class PeerProfile { const muted = appNotificationsManager.isPeerLocalMuted(peerId, false); this.notifications.checkboxField.checked = !muted; } else { - window.requestAnimationFrame(() => { + fastRaf(() => { this.notifications.container.style.display = 'none'; }); } @@ -901,6 +910,12 @@ export default class AppSharedMediaTab extends SliderSuperTab { } }); + rootScope.addEventListener('contacts_update', (userId) => { + if(this.peerId === userId && rootScope.myId !== userId) { + this.editBtn.classList.toggle('hide', !appUsersManager.isContact(userId)); + } + }); + //this.container.prepend(this.closeBtn.parentElement); this.searchSuper = new AppSearchSuper({ @@ -1113,7 +1128,7 @@ export default class AppSharedMediaTab extends SliderSuperTab { // const perf = performance.now(); this.profile.cleanupHTML(); - this.editBtn.style.display = 'none'; + this.editBtn.classList.add('hide'); this.searchSuper.cleanupHTML(true); @@ -1162,12 +1177,12 @@ export default class AppSharedMediaTab extends SliderSuperTab { if(this.peerId > 0) { if(this.peerId !== rootScope.myId && appUsersManager.isContact(this.peerId)) { - this.editBtn.style.display = ''; + this.editBtn.classList.remove('hide'); } } else { const chat: Chat = appChatsManager.getChat(-this.peerId); if((chat._ === 'chat' || (chat as Chat.channel).admin_rights) && !(chat as Chat.chat).pFlags.deactivated) { - this.editBtn.style.display = ''; + this.editBtn.classList.remove('hide'); } } } diff --git a/src/components/sortedUserList.ts b/src/components/sortedUserList.ts index de58a889..41f6d75d 100644 --- a/src/components/sortedUserList.ts +++ b/src/components/sortedUserList.ts @@ -21,8 +21,8 @@ type SortedUser = { }; export default class SortedUserList { protected static SORT_INTERVAL = 30e3; - public users: Map; - public sorted: Array; + protected users: Map; + protected sorted: Array; public list: HTMLUListElement; protected lazyLoadQueue: LazyLoadQueueIntersector; @@ -116,7 +116,11 @@ export default class SortedUserList { user.dom.listEl.remove(); this.users.delete(peerId); - this.sorted.findAndSplice(_user => _user === user); + + const idx = this.sorted.indexOf(user); + if(idx !== -1) { + this.sorted.splice(idx, 1); + } } public update(peerId: number, batch = false) { diff --git a/src/lib/appManagers/appAvatarsManager.ts b/src/lib/appManagers/appAvatarsManager.ts index 4bb8c0d3..d900ee9a 100644 --- a/src/lib/appManagers/appAvatarsManager.ts +++ b/src/lib/appManagers/appAvatarsManager.ts @@ -23,6 +23,10 @@ export class AppAvatarsManager { [size in PeerPhotoSize]?: string | Promise } } = {}; + + public isAvatarCached(peerId: number) { + return !!this.savedAvatarURLs[peerId]; + } public removeFromAvatarsCache(peerId: number) { if(this.savedAvatarURLs[peerId]) { diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index 782e87a4..71ba5b28 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -51,7 +51,7 @@ import PeerTitle from "../../components/peerTitle"; import { forEachReverse } from "../../helpers/array"; import htmlToDocumentFragment from "../../helpers/dom/htmlToDocumentFragment"; import htmlToSpan from "../../helpers/dom/htmlToSpan"; -import { REPLIES_PEER_ID } from "../mtproto/mtproto_config"; +import { REPLIES_PEER_ID, SERVICE_PEER_ID } from "../mtproto/mtproto_config"; import formatCallDuration from "../../helpers/formatCallDuration"; import appAvatarsManager from "./appAvatarsManager"; import telegramMeWebManager from "../mtproto/telegramMeWebManager"; @@ -4443,7 +4443,7 @@ export class AppMessagesManager { private onUpdateServiceNotification = (update: Update.updateServiceNotification) => { //this.log('updateServiceNotification', update); - const fromId = 777000; + const fromId = SERVICE_PEER_ID; const peerId = fromId; const messageId = this.generateTempMessageId(peerId); const message: any = { diff --git a/src/lib/appManagers/appUsersManager.ts b/src/lib/appManagers/appUsersManager.ts index 3bf19fc3..3c798b1e 100644 --- a/src/lib/appManagers/appUsersManager.ts +++ b/src/lib/appManagers/appUsersManager.ts @@ -21,7 +21,7 @@ import { Chat, InputContact, InputUser, User as MTUser, UserProfilePhoto, UserSt import I18n, { i18n, LangPackKey } from "../langPack"; //import apiManager from '../mtproto/apiManager'; import apiManager from '../mtproto/mtprotoworker'; -import { REPLIES_PEER_ID } from "../mtproto/mtproto_config"; +import { REPLIES_PEER_ID, SERVICE_PEER_ID } from "../mtproto/mtproto_config"; import serverTimeManager from "../mtproto/serverTimeManager"; import { RichTextProcessor } from "../richtextprocessor"; import rootScope from "../rootScope"; @@ -371,15 +371,31 @@ export class AppUsersManager { // * exclude from state // defineNotNumerableProperties(user, ['initials', 'num', 'rFirstName', 'rFullName', 'rPhone', 'sortName', 'sortStatus']); - const fullName = user.first_name + ' ' + (user.last_name || ''); - if(user.username) { - const searchUsername = cleanUsername(user.username); - this.usernames[searchUsername] = userId; + if(!oldUser || oldUser.username !== user.username) { + if(oldUser?.username) { + const oldSearchUsername = cleanUsername(oldUser.username); + delete this.usernames[oldSearchUsername]; + } + + if(user.username) { + const searchUsername = cleanUsername(user.username); + this.usernames[searchUsername] = userId; + } } - user.sortName = user.pFlags.deleted ? '' : cleanSearchText(fullName, false); + if(!oldUser + || oldUser.initials === undefined + || oldUser.sortName === undefined + || oldUser.first_name !== user.first_name + || oldUser.last_name !== user.last_name) { + const fullName = user.first_name + ' ' + (user.last_name || ''); - user.initials = RichTextProcessor.getAbbreviation(fullName); + user.sortName = user.pFlags.deleted ? '' : cleanSearchText(fullName, false); + user.initials = RichTextProcessor.getAbbreviation(fullName); + } else { + user.sortName = oldUser.sortName; + user.initials = oldUser.initials; + } if(user.status) { if((user.status as UserStatus.userStatusOnline).expires) { @@ -413,8 +429,15 @@ export class AppUsersManager { } */ + const wasContact = !!oldUser.pFlags.contact; + const newContact = !!user.pFlags.contact; + safeReplaceObject(oldUser, user); rootScope.dispatchEvent('user_update', userId); + + if(wasContact !== newContact) { + this.onContactUpdated(userId, newContact, wasContact); + } } if(changedPhoto) { @@ -497,7 +520,7 @@ export class AppUsersManager { case REPLIES_PEER_ID: key = 'Peer.RepliesNotifications'; break; - case 777000: + case SERVICE_PEER_ID: key = 'Peer.ServiceNotifications'; break; default: { @@ -599,7 +622,7 @@ export class AppUsersManager { public canSendToUser(id: number) { const user = this.getUser(id); - return !user.pFlags.deleted && user.username !== 'replies'; + return !user.pFlags.deleted && user.id !== REPLIES_PEER_ID; } public getUserPhoto(id: number) { @@ -632,19 +655,22 @@ export class AppUsersManager { const timestampNow = tsNow(true); for(const i in this.users) { const user = this.users[i]; - - if(user.status && - user.status._ === 'userStatusOnline' && - user.status.expires < timestampNow) { - - user.status = {_: 'userStatusOffline', was_online: user.status.expires}; - rootScope.dispatchEvent('user_update', user.id); - - this.setUserToStateIfNeeded(user); - } + this.updateUserStatus(user, timestampNow); } }; + public updateUserStatus(user: MTUser.user, timestampNow = tsNow(true)) { + if(user.status && + user.status._ === 'userStatusOnline' && + user.status.expires < timestampNow) { + + user.status = {_: 'userStatusOffline', was_online: user.status.expires}; + rootScope.dispatchEvent('user_update', user.id); + + this.setUserToStateIfNeeded(user); + } + } + public forceUserOnline(id: number, eventTimestamp?: number) { if(this.isBot(id)) { return; @@ -819,8 +845,7 @@ export class AppUsersManager { }); } - private onContactUpdated(userId: number, isContact: boolean) { - const curIsContact = this.isContact(userId); + private onContactUpdated(userId: number, isContact: boolean, curIsContact = this.isContact(userId)) { if(isContact !== curIsContact) { if(isContact) { this.pushContact(userId); diff --git a/src/lib/mtproto/mtproto_config.ts b/src/lib/mtproto/mtproto_config.ts index 093effd3..60f6443b 100644 --- a/src/lib/mtproto/mtproto_config.ts +++ b/src/lib/mtproto/mtproto_config.ts @@ -10,3 +10,4 @@ export type UserAuth = {dcID: number | string, date: number, id: number}; export const REPLIES_PEER_ID = 1271266957; +export const SERVICE_PEER_ID = 777000; diff --git a/src/scss/partials/_autocompletePeerHelper.scss b/src/scss/partials/_autocompletePeerHelper.scss index 17e4428c..34009172 100644 --- a/src/scss/partials/_autocompletePeerHelper.scss +++ b/src/scss/partials/_autocompletePeerHelper.scss @@ -17,7 +17,7 @@ height: 3.125rem; display: flex; // padding: 0 .75rem; - padding: 0 2.125rem 0px 0.75rem; + padding: 0 2.125rem 0 0.75rem; align-items: center; cursor: pointer; user-select: none; @@ -35,6 +35,10 @@ margin-left: .5625rem; color: var(--secondary-text-color); } + + &-avatar { + flex: 0 0 auto; + } } } } diff --git a/src/scss/partials/_chatBubble.scss b/src/scss/partials/_chatBubble.scss index 98b974b2..a274757e 100644 --- a/src/scss/partials/_chatBubble.scss +++ b/src/scss/partials/_chatBubble.scss @@ -899,8 +899,7 @@ $bubble-margin: .25rem; } .text { - overflow: hidden; - text-overflow: ellipsis; + word-break: break-word; margin-top: 1px; font-size: var(--messages-secondary-text-size); } diff --git a/src/scss/partials/_input.scss b/src/scss/partials/_input.scss index 99b19096..5d768de4 100644 --- a/src/scss/partials/_input.scss +++ b/src/scss/partials/_input.scss @@ -58,6 +58,7 @@ pointer-events: none; margin-top: calc((var(--height) - 1.5rem) / 2); // * Center of first line user-select: none; + white-space: nowrap; @include animation-level(2) { transition: .2s transform, .2s padding, .1s opacity, font-weight 0s .1s; diff --git a/src/scss/partials/popups/_createPoll.scss b/src/scss/partials/popups/_createPoll.scss index 1a8c0fdf..483d75b5 100644 --- a/src/scss/partials/popups/_createPoll.scss +++ b/src/scss/partials/popups/_createPoll.scss @@ -70,10 +70,12 @@ } .poll-create-questions { - padding: 0px 1.25rem 1.5rem; + padding: 0 1.25rem 1.5rem; - .input-field-input { - padding-right: 3.25rem; + &:not(:last-child) { + .input-field-input { + padding-right: 3.25rem; + } } } diff --git a/src/scss/partials/popups/_mediaAttacher.scss b/src/scss/partials/popups/_mediaAttacher.scss index 6d837796..394db2ee 100644 --- a/src/scss/partials/popups/_mediaAttacher.scss +++ b/src/scss/partials/popups/_mediaAttacher.scss @@ -52,6 +52,10 @@ } } } + + img, video { + border-radius: inherit; + } } &-header {