Browse Source

Chatlist media preview

master
Eduard Kuzmenko 3 years ago
parent
commit
35d8775e77
  1. 2
      src/components/appSearchSuper..ts
  2. 2
      src/components/appSelectPeers.ts
  3. 4
      src/components/scrollable.ts
  4. 13
      src/components/singleTransition.ts
  5. 2
      src/config/app.ts
  6. 25
      src/helpers/dom/renderImageFromUrl.ts
  7. 282
      src/lib/appManagers/appDialogsManager.ts
  8. 10
      src/lib/appManagers/appMessagesManager.ts
  9. 31
      src/lib/appManagers/appUsersManager.ts
  10. 81
      src/lib/storages/dialogs.ts
  11. 8
      src/scss/partials/_chatBubble.scss
  12. 11
      src/scss/partials/_chatlist.scss

2
src/components/appSearchSuper..ts

@ -803,7 +803,7 @@ export default class AppSearchSuper { @@ -803,7 +803,7 @@ export default class AppSearchSuper {
}
}),
appMessagesManager.getConversations(query, 0, 20, 0)
appMessagesManager.getConversations(query, 0, 20, 0).promise
.then(onLoad)
.then(value => {
if(value) {

2
src/components/appSelectPeers.ts

@ -261,7 +261,7 @@ export default class AppSelectPeers { @@ -261,7 +261,7 @@ export default class AppSelectPeers {
const pageCount = windowSize.windowH / 72 * 1.25 | 0;
const tempId = this.getTempId('dialogs');
const promise = appMessagesManager.getConversations(this.query, this.offsetIndex, pageCount, this.folderId, true);
const promise = appMessagesManager.getConversations(this.query, this.offsetIndex, pageCount, this.folderId, true).promise;
this.promise = promise;
const value = await promise;
if(this.tempIds.dialogs !== tempId) {

4
src/components/scrollable.ts

@ -212,11 +212,11 @@ export default class Scrollable extends ScrollableBase { @@ -212,11 +212,11 @@ export default class Scrollable extends ScrollableBase {
}
};
public prepend(...elements: HTMLElement[]) {
public prepend(...elements: (HTMLElement | DocumentFragment)[]) {
(this.splitUp || this.padding || this.container).prepend(...elements);
}
public append(...elements: HTMLElement[]) {
public append(...elements: (HTMLElement | DocumentFragment)[]) {
(this.splitUp || this.padding || this.container).append(...elements);
}

13
src/components/singleTransition.ts

@ -6,7 +6,14 @@ @@ -6,7 +6,14 @@
import rootScope from "../lib/rootScope";
const SetTransition = (element: HTMLElement, className: string, forwards: boolean, duration: number, onTransitionEnd?: () => void, useRafs?: number) => {
const SetTransition = (
element: HTMLElement,
className: string,
forwards: boolean,
duration: number,
onTransitionEnd?: () => void,
useRafs?: number
) => {
const {timeout, raf} = element.dataset;
if(timeout !== undefined) {
clearTimeout(+timeout);
@ -19,7 +26,7 @@ const SetTransition = (element: HTMLElement, className: string, forwards: boolea @@ -19,7 +26,7 @@ const SetTransition = (element: HTMLElement, className: string, forwards: boolea
}
}
if(useRafs) {
if(useRafs && rootScope.settings.animationsEnabled && duration) {
element.dataset.raf = '' + window.requestAnimationFrame(() => {
delete element.dataset.raf;
SetTransition(element, className, forwards, duration, onTransitionEnd, useRafs - 1);
@ -43,7 +50,7 @@ const SetTransition = (element: HTMLElement, className: string, forwards: boolea @@ -43,7 +50,7 @@ const SetTransition = (element: HTMLElement, className: string, forwards: boolea
onTransitionEnd && onTransitionEnd();
};
if(!rootScope.settings.animationsEnabled) {
if(!rootScope.settings.animationsEnabled || !duration) {
element.classList.remove('animating', 'backwards');
afterTimeout();
return;

2
src/config/app.ts

@ -16,7 +16,7 @@ export const MAIN_DOMAIN = 'web.telegram.org'; @@ -16,7 +16,7 @@ export const MAIN_DOMAIN = 'web.telegram.org';
const App = {
id: 1025907,
hash: '452b0359b988148995f22ff0f4229750',
version: '0.8.0',
version: '0.8.1',
langPackVersion: '0.3.3',
langPack: 'macos',
langPackCode: 'en',

25
src/helpers/dom/renderImageFromUrl.ts

@ -4,6 +4,8 @@ @@ -4,6 +4,8 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
// import { getHeavyAnimationPromise } from "../../hooks/useHeavyAnimationCheck";
export const loadedURLs: {[url: string]: boolean} = {};
const set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string) => {
if(elem instanceof HTMLImageElement || elem instanceof HTMLVideoElement) elem.src = url;
@ -12,8 +14,12 @@ const set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoE @@ -12,8 +14,12 @@ const set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoE
};
// проблема функции в том, что она не подходит для ссылок, пригодна только для blob'ов, потому что обычным ссылкам нужен 'load' каждый раз.
export default function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement,
url: string, callback?: (err?: Event) => void, useCache = true) {
export default function renderImageFromUrl(
elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement,
url: string,
callback?: (err?: Event) => void,
useCache = true
) {
if(!url) {
console.error('renderImageFromUrl: no url?', elem, url);
callback && callback();
@ -26,6 +32,7 @@ export default function renderImageFromUrl(elem: HTMLElement | HTMLImageElement @@ -26,6 +32,7 @@ export default function renderImageFromUrl(elem: HTMLElement | HTMLImageElement
}
callback && callback();
// callback && getHeavyAnimationPromise().then(() => callback());
} else {
const isImage = elem instanceof HTMLImageElement;
const loader = isImage ? elem as HTMLImageElement : new Image();
@ -39,16 +46,10 @@ export default function renderImageFromUrl(elem: HTMLElement | HTMLImageElement @@ -39,16 +46,10 @@ export default function renderImageFromUrl(elem: HTMLElement | HTMLImageElement
loadedURLs[url] = true;
//console.log('onload:', url, performance.now() - perf);
if(callback) {
// TODO: переделать прогрузки аватаров до начала анимации, иначе с этим ожиданием они неприятно появляются
/* getHeavyAnimationPromise().then(() => {
callback();
}); */
callback();
}
//callback && callback();
});
// TODO: переделать прогрузки аватаров до начала анимации, иначе с этим ожиданием они неприятно появляются
// callback && getHeavyAnimationPromise().then(() => callback());
callback && callback();
}, {once: true});
if(callback) {
loader.addEventListener('error', callback);

282
src/lib/appManagers/appDialogsManager.ts

@ -4,6 +4,9 @@ @@ -4,6 +4,9 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import type DialogsStorage from "../storages/dialogs";
import type {MyDialogFilter as DialogFilter, MyDialogFilter} from "../storages/filters";
import type { LazyLoadQueueIntersector } from "../../components/lazyLoadQueue";
import AvatarElement from "../../components/avatar";
import DialogsContextMenu from "../../components/dialogsContextMenu";
import { horizontalMenu } from "../../components/horizontalMenu";
@ -20,7 +23,6 @@ import apiUpdatesManager from "./apiUpdatesManager"; @@ -20,7 +23,6 @@ import apiUpdatesManager from "./apiUpdatesManager";
import appPeersManager from './appPeersManager';
import appImManager from "./appImManager";
import appMessagesManager, { Dialog } from "./appMessagesManager";
import {MyDialogFilter as DialogFilter, MyDialogFilter} from "../storages/filters";
import appStateManager, { State } from "./appStateManager";
import appUsersManager from "./appUsersManager";
import Button from "../../components/button";
@ -31,7 +33,6 @@ import appNotificationsManager from "./appNotificationsManager"; @@ -31,7 +33,6 @@ import appNotificationsManager from "./appNotificationsManager";
import PeerTitle from "../../components/peerTitle";
import I18n, { FormatterArguments, i18n, LangPackKey, _i18n } from "../langPack";
import findUpTag from "../../helpers/dom/findUpTag";
import { LazyLoadQueueIntersector } from "../../components/lazyLoadQueue";
import lottieLoader from "../lottieLoader";
import { wrapLocalSticker, wrapPhoto } from "../../components/wrappers";
import AppEditFolderTab from "../../components/sidebarLeft/tabs/editFolder";
@ -48,8 +49,8 @@ import { isTouchSupported } from "../../helpers/touchSupport"; @@ -48,8 +49,8 @@ import { isTouchSupported } from "../../helpers/touchSupport";
import handleTabSwipe from "../../helpers/dom/handleTabSwipe";
import windowSize from "../../helpers/windowSize";
import isInDOM from "../../helpers/dom/isInDOM";
import appPhotosManager from "./appPhotosManager";
import DialogsStorage from "../storages/dialogs";
import appPhotosManager, { MyPhoto } from "./appPhotosManager";
import { MyDocument } from "./appDocsManager";
export type DialogDom = {
avatarEl: AvatarElement,
@ -515,7 +516,7 @@ export class AppDialogsManager { @@ -515,7 +516,7 @@ export class AppDialogsManager {
if(this.isDialogMustBeInViewport(dialog)) {
if(!this.doms.hasOwnProperty(dialog.peerId)) {
const ret = this.addDialogNew({dialog});
const ret = this.addListDialog({dialog});
if(ret) {
const idx = appMessagesManager.getDialogByPeerId(dialog.peerId)[1];
positionElementByIndex(ret.dom.listEl, this.chatList, idx);
@ -749,11 +750,12 @@ export class AppDialogsManager { @@ -749,11 +750,12 @@ export class AppDialogsManager {
try {
//console.time('getDialogs time');
const getConversationPromise = (this.filterId > 1 ? appUsersManager.getContacts() as Promise<any> : Promise.resolve()).then(() => {
return appMessagesManager.getConversations('', offsetIndex, loadCount, filterId, true);
});
const getConversationsResult = appMessagesManager.getConversations('', offsetIndex, loadCount, filterId, true);
if(getConversationsResult.cached) {
this.chatsPreloader.remove();
}
const result = await getConversationPromise;
const result = await getConversationsResult.promise;
if(this.loadDialogsPromise !== promise) {
return;
@ -779,12 +781,26 @@ export class AppDialogsManager { @@ -779,12 +781,26 @@ export class AppDialogsManager {
if(result.dialogs.length) {
const dialogs = side === 'top' ? result.dialogs.slice().reverse() : result.dialogs;
const container = document.createDocumentFragment();
const loadPromises: Promise<any>[] = [];
const append = side === 'bottom';
dialogs.forEach((dialog) => {
this.addDialogNew({
this.addListDialog({
dialog,
append: side === 'bottom'
container,
append,
loadPromises,
isBatch: true
});
});
if(container.childElementCount) {
if(loadPromises.length) {
await Promise.all(loadPromises).finally();
}
this.scroll[append ? 'append' : 'prepend'](container);
}
}
const offsetDialog = result.dialogs[side === 'top' ? 0 : result.dialogs.length - 1];
@ -1215,11 +1231,28 @@ export class AppDialogsManager { @@ -1215,11 +1231,28 @@ export class AppDialogsManager {
private reorderDialogs() {
//const perf = performance.now();
if(this.reorderDialogsTimeout) {
window.cancelAnimationFrame(this.reorderDialogsTimeout);
return;
}
if(this.loadDialogsPromise) {
this.loadDialogsPromise.then(() => {
this.reorderDialogs();
});
return;
}
this.reorderDialogsTimeout = window.requestAnimationFrame(() => {
this.reorderDialogsTimeout = 0;
if(this.loadDialogsPromise) {
this.loadDialogsPromise.then(() => {
this.reorderDialogs();
});
return;
}
const dialogs = appMessagesManager.dialogsStorage.getFolder(this.filterId);
const currentOrder = (Array.from(this.chatList.children) as HTMLElement[]).map(el => +el.dataset.peerId);
@ -1253,7 +1286,14 @@ export class AppDialogsManager { @@ -1253,7 +1286,14 @@ export class AppDialogsManager {
});
}
public setLastMessage(dialog: Dialog, lastMessage?: any, dom?: DialogDom, highlightWord?: string) {
public setLastMessage(
dialog: Dialog,
lastMessage?: any,
dom?: DialogDom,
highlightWord?: string,
loadPromises?: Promise<any>[],
isBatch = false
) {
///////console.log('setlastMessage:', lastMessage);
if(!dom) {
dom = this.getDialogDom(dialog.peerId);
@ -1280,7 +1320,6 @@ export class AppDialogsManager { @@ -1280,7 +1320,6 @@ export class AppDialogsManager {
return;
}
const peer = dialog.peer;
const peerId = dialog.peerId;
//let peerId = appMessagesManager.getMessagePeer(lastMessage);
@ -1288,35 +1327,49 @@ export class AppDialogsManager { @@ -1288,35 +1327,49 @@ export class AppDialogsManager {
/* if(!dom.lastMessageSpan.classList.contains('user-typing')) */ {
let mediaContainer: HTMLElement;
if(!lastMessage.deleted && !draftMessage) {
const media: MyDocument | MyPhoto = appMessagesManager.getMediaFromMessage(lastMessage);
if(media && (media._ === 'photo' || (['video', 'gif'] as MyDocument['type'][]).includes(media.type))) {
const size = appPhotosManager.choosePhotoSize(media, 20, 20);
if(size._ !== 'photoSizeEmpty') {
mediaContainer = document.createElement('div');
mediaContainer.classList.add('dialog-subtitle-media');
wrapPhoto({
photo: media,
message: lastMessage,
container: mediaContainer,
withoutPreloader: true,
size,
loadPromises
});
if((media as MyDocument).type === 'video') {
const playIcon = document.createElement('span');
playIcon.classList.add('tgico-play');
mediaContainer.append(playIcon);
}
}
}
}
const withoutMediaType = !!mediaContainer && !!lastMessage?.message;
let fragment: DocumentFragment;
if(highlightWord && lastMessage.message) {
fragment = appMessagesManager.wrapMessageForReply(lastMessage, undefined, undefined, false, highlightWord);
fragment = appMessagesManager.wrapMessageForReply(lastMessage, undefined, undefined, false, highlightWord, withoutMediaType);
} else if(draftMessage) {
fragment = appMessagesManager.wrapMessageForReply(draftMessage);
} else if(!lastMessage.deleted) {
fragment = appMessagesManager.wrapMessageForReply(lastMessage);
fragment = appMessagesManager.wrapMessageForReply(lastMessage, undefined, undefined, false, undefined, withoutMediaType);
}
/* if(!lastMessage.deleted && !draftMessage) {
const photo = lastMessage.media?.photo;
if(photo) {
const div = document.createElement('div');
div.classList.add('dialog-subtitle-media');
const size = appPhotosManager.choosePhotoSize(photo, 20, 20);
wrapPhoto({
photo,
message: lastMessage,
container: div,
withoutPreloader: true,
size
});
fragment.prepend(div);
// dom.subtitleEl.prepend(div);
}
} */
if(mediaContainer) {
fragment.prepend(mediaContainer);
}
replaceContent(dom.lastMessageSpan, fragment);
@ -1326,7 +1379,7 @@ export class AppDialogsManager { @@ -1326,7 +1379,7 @@ export class AppDialogsManager {
bold.classList.add('danger');
bold.append(i18n('Draft'), ': ');
dom.lastMessageSpan.prepend(bold);
} else if(peer._ !== 'peerUser' && peerId !== lastMessage.fromId && !lastMessage.action) {
} else if(peerId < 0 && peerId !== lastMessage.fromId && !lastMessage.action) {
const sender = appPeersManager.getPeer(lastMessage.fromId);
if(sender && sender.id) {
const senderBold = document.createElement('b');
@ -1354,13 +1407,13 @@ export class AppDialogsManager { @@ -1354,13 +1407,13 @@ export class AppDialogsManager {
} else dom.lastTimeSpan.textContent = '';
if(this.doms[peerId] === dom) {
this.setUnreadMessages(dialog);
this.setUnreadMessages(dialog, isBatch);
} else { // means search
dom.listEl.dataset.mid = lastMessage.mid;
}
}
private setUnreadMessages(dialog: Dialog) {
private setUnreadMessages(dialog: Dialog, isBatch = false) {
const dom = this.getDialogDom(dialog.peerId);
if(dialog.folder_id === 1) {
@ -1372,10 +1425,12 @@ export class AppDialogsManager { @@ -1372,10 +1425,12 @@ export class AppDialogsManager {
return;
}
const isMuted = appNotificationsManager.isPeerLocalMuted(dialog.peerId, true);
const wasMuted = dom.listEl.classList.contains('is-muted');
if(isMuted !== wasMuted) {
SetTransition(dom.listEl, 'is-muted', isMuted, 200);
if(!isBatch) {
const isMuted = appNotificationsManager.isPeerLocalMuted(dialog.peerId, true);
const wasMuted = dom.listEl.classList.contains('is-muted');
if(isMuted !== wasMuted) {
SetTransition(dom.listEl, 'is-muted', isMuted, 200);
}
}
const lastMessage = dialog.draft?._ === 'draftMessage' ?
@ -1422,12 +1477,14 @@ export class AppDialogsManager { @@ -1422,12 +1477,14 @@ export class AppDialogsManager {
}
}
SetTransition(dom.unreadBadge, 'is-visible', hasUnreadBadge, 200, hasUnreadBadge ? undefined : () => {
const transitionDuration = isBatch ? 0 : 200;
SetTransition(dom.unreadBadge, 'is-visible', hasUnreadBadge, transitionDuration, hasUnreadBadge ? undefined : () => {
dom.unreadBadge.remove();
}, !isUnreadBadgeMounted ? 2 : 0);
if(dom.mentionsBadge) {
SetTransition(dom.mentionsBadge, 'is-visible', hasMentionsBadge, 200, hasMentionsBadge ? undefined : () => {
SetTransition(dom.mentionsBadge, 'is-visible', hasMentionsBadge, transitionDuration, hasMentionsBadge ? undefined : () => {
dom.mentionsBadge.remove();
delete dom.mentionsBadge;
}, !isMentionBadgeMounted ? 2 : 0);
@ -1474,9 +1531,55 @@ export class AppDialogsManager { @@ -1474,9 +1531,55 @@ export class AppDialogsManager {
return this.doms[peerId];
}
private getDialog(dialog: Dialog | number): Dialog {
if(typeof(dialog) === 'number') {
const originalDialog = appMessagesManager.getDialogOnly(dialog);
if(!originalDialog) {
return {
peerId: dialog,
peer: appPeersManager.getOutputPeer(dialog),
pFlags: {}
} as any;
}
return originalDialog;
}
return dialog;
}
private addListDialog(options: Parameters<AppDialogsManager['addDialogNew']>[0] & {isBatch?: boolean}) {
const dialog = this.getDialog(options.dialog);
const filter = appMessagesManager.filtersStorage.getFilter(this.filterId);
if((filter && !appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) ||
(!filter && this.filterId !== dialog.folder_id)) {
return;
}
if(!options.container) {
options.container = this.scroll;
}
const ret = this.addDialogNew(options);
if(ret) {
this.doms[dialog.peerId] = ret.dom;
const isMuted = appNotificationsManager.isPeerLocalMuted(dialog.peerId, true);
if(isMuted) {
ret.dom.listEl.classList.add('is-muted');
}
this.setLastMessage(dialog, undefined, undefined, undefined, options.loadPromises, options.isBatch);
}
return ret;
}
public addDialogNew(options: {
dialog: Dialog | number,
container?: HTMLUListElement | Scrollable | false,
container?: Parameters<AppDialogsManager['addDialog']>[1],
drawStatus?: boolean,
rippleEnabled?: boolean,
onlyFirstName?: boolean,
@ -1485,60 +1588,40 @@ export class AppDialogsManager { @@ -1485,60 +1588,40 @@ export class AppDialogsManager {
avatarSize?: number,
autonomous?: boolean,
lazyLoadQueue?: LazyLoadQueueIntersector,
loadPromises?: Promise<any>[]
}) {
return this.addDialog(options.dialog, options.container, options.drawStatus, options.rippleEnabled, options.onlyFirstName, options.meAsSaved, options.append, options.avatarSize, options.autonomous, options.lazyLoadQueue);
return this.addDialog(options.dialog, options.container, options.drawStatus, options.rippleEnabled, options.onlyFirstName, options.meAsSaved, options.append, options.avatarSize, options.autonomous, options.lazyLoadQueue, options.loadPromises);
}
public addDialog(_dialog: Dialog | number, container?: HTMLUListElement | Scrollable | false, drawStatus = true, rippleEnabled = true, onlyFirstName = false, meAsSaved = true, append = true, avatarSize = 54, autonomous = !!container, lazyLoadQueue?: LazyLoadQueueIntersector) {
let dialog: Dialog;
if(typeof(_dialog) === 'number') {
let originalDialog = appMessagesManager.getDialogOnly(_dialog);
if(!originalDialog) {
originalDialog = {
peerId: _dialog,
peer: appPeersManager.getOutputPeer(_dialog),
pFlags: {}
} as any;
}
dialog = originalDialog;
} else {
dialog = _dialog;
}
const peerId: number = dialog.peerId;
if(container === undefined) {
if(this.doms[peerId] || dialog.migratedTo !== undefined) return;
const filter = appMessagesManager.filtersStorage.getFilter(this.filterId);
if((filter && !appMessagesManager.filtersStorage.testDialogForFilter(dialog, filter)) || (!filter && this.filterId !== dialog.folder_id)) {
return;
}
}
public addDialog(_dialog: Dialog | number,
container?: HTMLElement | Scrollable | DocumentFragment | false,
drawStatus = true,
rippleEnabled = true,
onlyFirstName = false,
meAsSaved = true,
append = true,
avatarSize = 54,
autonomous = !!container,
lazyLoadQueue?: LazyLoadQueueIntersector,
loadPromises?: Promise<any>[]) {
const dialog = this.getDialog(_dialog);
const peerId = dialog.peerId;
const avatarEl = new AvatarElement();
avatarEl.loadPromises = loadPromises;
avatarEl.lazyLoadQueue = lazyLoadQueue;
avatarEl.setAttribute('dialog', meAsSaved ? '1' : '0');
avatarEl.setAttribute('peer', '' + peerId);
avatarEl.classList.add('dialog-avatar', 'avatar-' + avatarSize);
if(drawStatus && peerId !== rootScope.myId && dialog.peer) {
const peer = dialog.peer;
switch(peer._) {
case 'peerUser':
const user = appUsersManager.getUser(peerId);
//console.log('found user', user);
if(user.status && user.status._ === 'userStatusOnline') {
avatarEl.classList.add('is-online');
}
break;
default:
break;
if(drawStatus && peerId !== rootScope.myId) {
if(peerId > 0) {
const user = appUsersManager.getUser(peerId);
//console.log('found user', user);
if(user.status && user.status._ === 'userStatusOnline') {
avatarEl.classList.add('is-online');
}
}
}
@ -1629,23 +1712,8 @@ export class AppDialogsManager { @@ -1629,23 +1712,8 @@ export class AppDialogsManager {
good = true;
}
} */
const method: 'append' | 'prepend' = append ? 'append' : 'prepend';
if(container === undefined/* || good */) {
this.scroll[method](li);
this.doms[dialog.peerId] = dom;
/* if(container) {
container.append(li);
} */
const isMuted = appNotificationsManager.isPeerLocalMuted(dialog.peerId, true);
if(isMuted) {
li.classList.add('is-muted');
}
this.setLastMessage(dialog);
} else if(container) {
if(container) {
const method = append ? 'append' : 'prepend';
container[method](li);
}

10
src/lib/appManagers/appMessagesManager.ts

@ -1639,7 +1639,7 @@ export class AppMessagesManager { @@ -1639,7 +1639,7 @@ export class AppMessagesManager {
for(; folderId < 2; ++folderId) {
let offsetIndex = 0;
for(;;) {
const {dialogs} = await appMessagesManager.getConversations(query, offsetIndex, limit, folderId);
const {dialogs} = await appMessagesManager.getConversations(query, offsetIndex, limit, folderId).promise;
if(dialogs.length) {
outDialogs.push(...dialogs);
@ -2508,9 +2508,9 @@ export class AppMessagesManager { @@ -2508,9 +2508,9 @@ export class AppMessagesManager {
message.totalEntities = RichTextProcessor.mergeEntities(apiEntities, myEntities); // ! only in this order, otherwise bold and emoji formatting won't work
}
public wrapMessageForReply(message: any, text: string, usingMids: number[], plain: true, highlightWord?: string): string;
public wrapMessageForReply(message: any, text?: string, usingMids?: number[], plain?: false, highlightWord?: string): DocumentFragment;
public wrapMessageForReply(message: any, text: string = message.message, usingMids?: number[], plain?: boolean, highlightWord?: string): DocumentFragment | string {
public wrapMessageForReply(message: any, text: string, usingMids: number[], plain: true, highlightWord?: string, withoutMediaType?: boolean): string;
public wrapMessageForReply(message: any, text?: string, usingMids?: number[], plain?: false, highlightWord?: string, withoutMediaType?: boolean): DocumentFragment;
public wrapMessageForReply(message: any, text: string = message.message, usingMids?: number[], plain?: boolean, highlightWord?: string, withoutMediaType?: boolean): DocumentFragment | string {
const parts: (HTMLElement | string)[] = [];
const addPart = (langKey: LangPackKey, part?: string | HTMLElement, text?: string) => {
@ -2557,7 +2557,7 @@ export class AppMessagesManager { @@ -2557,7 +2557,7 @@ export class AppMessagesManager {
usingFullAlbum = false;
}
if(!usingFullAlbum) {
if(!usingFullAlbum && !withoutMediaType) {
const media = message.media;
switch(media._) {
case 'messageMediaPhoto':

31
src/lib/appManagers/appUsersManager.ts

@ -11,6 +11,7 @@ @@ -11,6 +11,7 @@
import { MOUNT_CLASS_TO } from "../../config/debug";
import { filterUnique } from "../../helpers/array";
import { CancellablePromise, deferredPromise } from "../../helpers/cancellablePromise";
import cleanSearchText from "../../helpers/cleanSearchText";
import cleanUsername from "../../helpers/cleanUsername";
import { tsNow } from "../../helpers/date";
@ -42,7 +43,7 @@ export class AppUsersManager { @@ -42,7 +43,7 @@ export class AppUsersManager {
private users: {[userId: number]: User};
private usernames: {[username: string]: number};
private contactsIndex: SearchIndex<number>;
private contactsFillPromise: Promise<Set<number>>;
private contactsFillPromise: CancellablePromise<Set<number>>;
private contactsList: Set<number>;
private updatedContactsList: boolean;
@ -138,7 +139,8 @@ export class AppUsersManager { @@ -138,7 +139,8 @@ export class AppUsersManager {
});
if(contactsList.length) {
this.contactsFillPromise = Promise.resolve(this.contactsList);
this.contactsFillPromise = deferredPromise();
this.contactsFillPromise.resolve(this.contactsList);
}
}
@ -198,13 +200,19 @@ export class AppUsersManager { @@ -198,13 +200,19 @@ export class AppUsersManager {
public fillContacts() {
if(this.contactsFillPromise && this.updatedContactsList) {
return this.contactsFillPromise;
return {
cached: this.contactsFillPromise.isFulfilled,
promise: this.contactsFillPromise
};
}
this.updatedContactsList = true;
const promise = apiManager.invokeApi('contacts.getContacts').then((result) => {
const promise = deferredPromise<Set<number>>();
apiManager.invokeApi('contacts.getContacts').then((result) => {
if(result._ === 'contacts.contacts') {
this.contactsList.clear();
this.saveApiUsers(result.users);
result.contacts.forEach((contact) => {
@ -212,14 +220,19 @@ export class AppUsersManager { @@ -212,14 +220,19 @@ export class AppUsersManager {
});
this.onContactsModified();
}
this.contactsFillPromise = promise;
this.contactsFillPromise = promise;
}
return this.contactsList;
promise.resolve(this.contactsList);
}, () => {
this.updatedContactsList = false;
});
return this.contactsFillPromise || (this.contactsFillPromise = promise);
return {
cached: this.contactsFillPromise?.isFulfilled,
promise: this.contactsFillPromise || (this.contactsFillPromise = promise)
};
}
public resolveUsername(username: string): Promise<Chat | User> {
@ -265,7 +278,7 @@ export class AppUsersManager { @@ -265,7 +278,7 @@ export class AppUsersManager {
}
public getContacts(query?: string, includeSaved = false, sortBy: 'name' | 'online' | 'none' = 'name') {
return this.fillContacts().then(_contactsList => {
return this.fillContacts().promise.then(_contactsList => {
let contactsList = [..._contactsList];
if(query) {
const results = this.contactsIndex.search(query);

81
src/lib/storages/dialogs.ts

@ -599,7 +599,26 @@ export default class DialogsStorage { @@ -599,7 +599,26 @@ export default class DialogsStorage {
return indexStr;
}
public getDialogs(query = '', offsetIndex?: number, limit = 20, folderId = 0, skipMigrated = false) {
public getDialogs(query = '', offsetIndex?: number, limit = 20, folderId = 0, skipMigrated = false): {
cached: boolean;
promise: Promise<{
dialogs: Dialog[];
count: number;
isEnd: boolean;
}>;
} {
if(folderId > 1) {
const fillContactsResult = this.appUsersManager.fillContacts();
if(!fillContactsResult.cached) {
return {
cached: false,
promise: fillContactsResult.promise.then(() => {
return this.getDialogs(query, offsetIndex, limit, folderId, skipMigrated).promise;
})
};
}
}
const realFolderId = folderId > 1 ? 0 : folderId;
let curDialogStorage = this.getFolder(folderId, skipMigrated);
@ -641,37 +660,43 @@ export default class DialogsStorage { @@ -641,37 +660,43 @@ export default class DialogsStorage {
const loadedAll = this.isDialogsLoaded(realFolderId);
if(query || loadedAll || curDialogStorage.length >= offset + limit) {
return Promise.resolve({
dialogs: curDialogStorage.slice(offset, offset + limit),
count: loadedAll ? curDialogStorage.length : null,
isEnd: loadedAll && (offset + limit) >= curDialogStorage.length
});
return {
cached: true,
promise: Promise.resolve({
dialogs: curDialogStorage.slice(offset, offset + limit),
count: loadedAll ? curDialogStorage.length : null,
isEnd: loadedAll && (offset + limit) >= curDialogStorage.length
})
};
}
return this.appMessagesManager.getTopMessages(limit, realFolderId).then(result => {
//const curDialogStorage = this[folderId];
if(skipMigrated) {
curDialogStorage = this.getFolder(folderId, skipMigrated);
}
offset = 0;
if(offsetIndex > 0) {
for(let length = curDialogStorage.length; offset < length; ++offset) {
if(offsetIndex > curDialogStorage[offset][indexStr]) {
break;
return {
cached: false,
promise: this.appMessagesManager.getTopMessages(limit, realFolderId).then(result => {
//const curDialogStorage = this[folderId];
if(skipMigrated) {
curDialogStorage = this.getFolder(folderId, skipMigrated);
}
offset = 0;
if(offsetIndex > 0) {
for(let length = curDialogStorage.length; offset < length; ++offset) {
if(offsetIndex > curDialogStorage[offset][indexStr]) {
break;
}
}
}
}
//this.log.warn(offset, offset + limit, curDialogStorage.dialogs.length, this.dialogs.length);
return {
dialogs: curDialogStorage.slice(offset, offset + limit),
count: result.count === undefined ? curDialogStorage.length : result.count,
// isEnd: this.isDialogsLoaded(realFolderId) && (offset + limit) >= curDialogStorage.length
isEnd: result.isEnd
};
});
//this.log.warn(offset, offset + limit, curDialogStorage.dialogs.length, this.dialogs.length);
return {
dialogs: curDialogStorage.slice(offset, offset + limit),
count: result.count === undefined ? curDialogStorage.length : result.count,
// isEnd: this.isDialogsLoaded(realFolderId) && (offset + limit) >= curDialogStorage.length
isEnd: result.isEnd
};
})
};
}
// only 0 and 1 folders

8
src/scss/partials/_chatBubble.scss

@ -2049,6 +2049,14 @@ $bubble-margin: .25rem; @@ -2049,6 +2049,14 @@ $bubble-margin: .25rem;
}
}
&.sticker,
&.round,
&.emoji-big {
.message {
right: 0;
}
}
.reply-border,
.quote:before {
background-color: var(--message-out-primary-color);

11
src/scss/partials/_chatlist.scss

@ -307,6 +307,17 @@ ul.chatlist { @@ -307,6 +307,17 @@ ul.chatlist {
display: inline-block;
vertical-align: middle;
.tgico-play {
position: absolute;
z-index: 1;
color: #fff;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
line-height: 1;
font-size: .625rem;
}
.media-photo {
width: inherit;
height: inherit;

Loading…
Cancel
Save