Chat ranks
This commit is contained in:
parent
253dcf55b5
commit
33685bb13c
|
@ -29,7 +29,7 @@ import LazyLoadQueue from '../lazyLoadQueue';
|
||||||
import ListenerSetter from '../../helpers/listenerSetter';
|
import ListenerSetter from '../../helpers/listenerSetter';
|
||||||
import PollElement from '../poll';
|
import PollElement from '../poll';
|
||||||
import AudioElement from '../audio';
|
import AudioElement from '../audio';
|
||||||
import {Chat as MTChat, ChatInvite, Document, Message, MessageEntity, MessageMedia, MessageReplyHeader, Photo, PhotoSize, ReactionCount, ReplyMarkup, SponsoredMessage, Update, UrlAuthResult, User, WebPage} from '../../layer';
|
import {ChannelParticipant, Chat as MTChat, ChatInvite, ChatParticipant, Document, Message, MessageEntity, MessageMedia, MessageReplyHeader, Photo, PhotoSize, ReactionCount, ReplyMarkup, SponsoredMessage, Update, UrlAuthResult, User, WebPage} from '../../layer';
|
||||||
import {BOT_START_PARAM, NULL_PEER_ID, REPLIES_PEER_ID} from '../../lib/mtproto/mtproto_config';
|
import {BOT_START_PARAM, NULL_PEER_ID, REPLIES_PEER_ID} from '../../lib/mtproto/mtproto_config';
|
||||||
import {FocusDirection, ScrollStartCallbackDimensions} from '../../helpers/fastSmoothScroll';
|
import {FocusDirection, ScrollStartCallbackDimensions} from '../../helpers/fastSmoothScroll';
|
||||||
import useHeavyAnimationCheck, {getHeavyAnimationPromise, dispatchHeavyAnimationEvent, interruptHeavyAnimation} from '../../hooks/useHeavyAnimationCheck';
|
import useHeavyAnimationCheck, {getHeavyAnimationPromise, dispatchHeavyAnimationEvent, interruptHeavyAnimation} from '../../hooks/useHeavyAnimationCheck';
|
||||||
|
@ -127,9 +127,6 @@ import wrapUrl from '../../lib/richTextProcessor/wrapUrl';
|
||||||
import getMessageThreadId from '../../lib/appManagers/utils/messages/getMessageThreadId';
|
import getMessageThreadId from '../../lib/appManagers/utils/messages/getMessageThreadId';
|
||||||
import wrapTopicNameButton from '../wrappers/topicNameButton';
|
import wrapTopicNameButton from '../wrappers/topicNameButton';
|
||||||
import wrapMediaSpoiler, {onMediaSpoilerClick, toggleMediaSpoiler} from '../wrappers/mediaSpoiler';
|
import wrapMediaSpoiler, {onMediaSpoilerClick, toggleMediaSpoiler} from '../wrappers/mediaSpoiler';
|
||||||
import confirmationPopup from '../confirmationPopup';
|
|
||||||
import wrapPeerTitle from '../wrappers/peerTitle';
|
|
||||||
import {PopupPeerCheckboxOptions} from '../popups/peer';
|
|
||||||
import toggleDisability from '../../helpers/dom/toggleDisability';
|
import toggleDisability from '../../helpers/dom/toggleDisability';
|
||||||
import {copyTextToClipboard} from '../../helpers/clipboard';
|
import {copyTextToClipboard} from '../../helpers/clipboard';
|
||||||
import liteMode from '../../helpers/liteMode';
|
import liteMode from '../../helpers/liteMode';
|
||||||
|
@ -137,6 +134,7 @@ import getMediaDurationFromMessage from '../../lib/appManagers/utils/messages/ge
|
||||||
import wrapLocalSticker from '../wrappers/localSticker';
|
import wrapLocalSticker from '../wrappers/localSticker';
|
||||||
import {LottieAssetName} from '../../lib/rlottie/lottieLoader';
|
import {LottieAssetName} from '../../lib/rlottie/lottieLoader';
|
||||||
import clamp from '../../helpers/number/clamp';
|
import clamp from '../../helpers/number/clamp';
|
||||||
|
import getParticipantRank from '../../lib/appManagers/utils/chats/getParticipantRank';
|
||||||
|
|
||||||
export const USER_REACTIONS_INLINE = false;
|
export const USER_REACTIONS_INLINE = false;
|
||||||
const USE_MEDIA_TAILS = false;
|
const USE_MEDIA_TAILS = false;
|
||||||
|
@ -302,6 +300,9 @@ export default class ChatBubbles {
|
||||||
|
|
||||||
private batchProcessor: BatchProcessor<Awaited<ReturnType<ChatBubbles['safeRenderMessage']>>>;
|
private batchProcessor: BatchProcessor<Awaited<ReturnType<ChatBubbles['safeRenderMessage']>>>;
|
||||||
|
|
||||||
|
private ranks: Map<PeerId, ReturnType<typeof getParticipantRank>>;
|
||||||
|
private processRanks: Set<() => void>;
|
||||||
|
private canShowRanks: boolean;
|
||||||
// private reactions: Map<number, ReactionsElement>;
|
// private reactions: Map<number, ReactionsElement>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -3146,6 +3147,45 @@ export default class ChatBubbles {
|
||||||
this.preloader.attach(this.container);
|
this.preloader.attach(this.container);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(!samePeer) {
|
||||||
|
this.ranks = undefined;
|
||||||
|
this.processRanks = undefined;
|
||||||
|
this.canShowRanks = false;
|
||||||
|
|
||||||
|
if(this.chat.isChannel) {
|
||||||
|
this.canShowRanks = true;
|
||||||
|
const promise = this.managers.acknowledged.appProfileManager.getParticipants(this.peerId.toChatId(), {_: 'channelParticipantsAdmins'}, 100);
|
||||||
|
const ackedResult = await m(promise);
|
||||||
|
const setRanksPromise = ackedResult.result.then((channelParticipants) => {
|
||||||
|
const participants = channelParticipants.participants as (ChatParticipant.chatParticipantAdmin | ChannelParticipant.channelParticipantAdmin)[];
|
||||||
|
this.ranks = new Map();
|
||||||
|
participants.forEach((participant) => {
|
||||||
|
const rank = getParticipantRank(participant);
|
||||||
|
this.ranks.set(participant.user_id.toPeerId(), rank);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if(ackedResult.cached) {
|
||||||
|
try {
|
||||||
|
await setRanksPromise;
|
||||||
|
} catch(err) {
|
||||||
|
this.ranks = new Map();
|
||||||
|
this.log.error('ranks error', err);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const processRanks = this.processRanks = new Set();
|
||||||
|
setRanksPromise.then(() => {
|
||||||
|
if(this.processRanks !== processRanks) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
processRanks.forEach((callback) => callback());
|
||||||
|
this.processRanks = undefined;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* this.ladderDeferred && this.ladderDeferred.resolve();
|
/* this.ladderDeferred && this.ladderDeferred.resolve();
|
||||||
this.ladderDeferred = deferredPromise<void>(); */
|
this.ladderDeferred = deferredPromise<void>(); */
|
||||||
|
|
||||||
|
@ -5093,14 +5133,16 @@ export default class ChatBubbles {
|
||||||
|
|
||||||
const isForward = fwdFromId || fwdFrom;
|
const isForward = fwdFromId || fwdFrom;
|
||||||
if(isHidden) {
|
if(isHidden) {
|
||||||
// /////this.log('message to render hidden', message);
|
|
||||||
title = document.createElement('span');
|
title = document.createElement('span');
|
||||||
setInnerHTML(title, wrapEmojiText(fwdFrom.from_name));
|
setInnerHTML(title, wrapEmojiText(fwdFrom.from_name));
|
||||||
title.classList.add('peer-title');
|
title.classList.add('peer-title');
|
||||||
// title = fwdFrom.from_name;
|
|
||||||
bubble.classList.add('hidden-profile');
|
bubble.classList.add('hidden-profile');
|
||||||
} else {
|
} else {
|
||||||
title = new PeerTitle({peerId: fwdFromId || message.fromId, withPremiumIcon: !isForward, wrapOptions}).element;
|
title = new PeerTitle({
|
||||||
|
peerId: fwdFromId || message.fromId,
|
||||||
|
withPremiumIcon: !isForward,
|
||||||
|
wrapOptions
|
||||||
|
}).element;
|
||||||
}
|
}
|
||||||
|
|
||||||
let replyContainer: HTMLElement;
|
let replyContainer: HTMLElement;
|
||||||
|
@ -5218,6 +5260,30 @@ export default class ChatBubbles {
|
||||||
replyContainer.classList.add('floating-part');
|
replyContainer.classList.add('floating-part');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(title && !isHidden && !fwdFromId && this.canShowRanks) {
|
||||||
|
const processRank = () => {
|
||||||
|
const rank = this.ranks.get(message.fromId);
|
||||||
|
if(!rank) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
(title as HTMLElement).after(this.createBubbleNameRank(rank));
|
||||||
|
};
|
||||||
|
|
||||||
|
if(this.ranks) {
|
||||||
|
processRank();
|
||||||
|
} else {
|
||||||
|
const processRanks = this.processRanks;
|
||||||
|
processRanks.add(processRank);
|
||||||
|
|
||||||
|
middleware.onDestroy(() => {
|
||||||
|
processRanks.delete(processRank);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if(isForwardFromChannel) {
|
||||||
|
(title as HTMLElement).after(this.createBubbleNameRank(0));
|
||||||
|
}
|
||||||
|
|
||||||
if(topicNameButtonContainer && isStandaloneMedia) {
|
if(topicNameButtonContainer && isStandaloneMedia) {
|
||||||
if(!attachmentDiv) {
|
if(!attachmentDiv) {
|
||||||
this.log.error('no attachment div?', bubble, message);
|
this.log.error('no attachment div?', bubble, message);
|
||||||
|
@ -5340,6 +5406,15 @@ export default class ChatBubbles {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createBubbleNameRank(rank: ReturnType<typeof getParticipantRank> | 0) {
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.classList.add('bubble-name-rank');
|
||||||
|
span.append(typeof(rank) === 'number' ?
|
||||||
|
i18n(!rank ? 'Chat.ChannelBadge' : (rank === 1 ? 'Chat.OwnerBadge' : 'ChatAdmin')) :
|
||||||
|
rank);
|
||||||
|
return span;
|
||||||
|
}
|
||||||
|
|
||||||
private prepareToSaveScroll(reverse?: boolean) {
|
private prepareToSaveScroll(reverse?: boolean) {
|
||||||
const isMounted = !!this.chatInner.parentElement;
|
const isMounted = !!this.chatInner.parentElement;
|
||||||
if(!isMounted) {
|
if(!isMounted) {
|
||||||
|
|
|
@ -1104,7 +1104,7 @@ const lang = {
|
||||||
'one_value': '%d Comment',
|
'one_value': '%d Comment',
|
||||||
'other_value': '%d Comments'
|
'other_value': '%d Comments'
|
||||||
},
|
},
|
||||||
'Chat.TopicBadge': 'topic creator',
|
// 'Chat.TopicBadge': 'topic creator',
|
||||||
'ChatTitle.ReportMessages': 'Report Messages',
|
'ChatTitle.ReportMessages': 'Report Messages',
|
||||||
'Chat.Send.WithoutSound': 'Send Without Sound',
|
'Chat.Send.WithoutSound': 'Send Without Sound',
|
||||||
'Chat.Send.SetReminder': 'Set a Reminder',
|
'Chat.Send.SetReminder': 'Set a Reminder',
|
||||||
|
|
|
@ -300,7 +300,7 @@ export class AppProfileManager extends AppManager {
|
||||||
return this.getChannelParticipants(id, filter, limit, offset);
|
return this.getChannelParticipants(id, filter, limit, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.resolve(this.getChatFull(id)).then((chatFull) => {
|
return callbackify(this.getChatFull(id), (chatFull) => {
|
||||||
const chatParticipants = (chatFull as ChatFull.chatFull).participants;
|
const chatParticipants = (chatFull as ChatFull.chatFull).participants;
|
||||||
if(chatParticipants._ !== 'chatParticipants') {
|
if(chatParticipants._ !== 'chatParticipants') {
|
||||||
throw makeError('CHAT_PRIVATE');
|
throw makeError('CHAT_PRIVATE');
|
||||||
|
@ -330,7 +330,7 @@ export class AppProfileManager extends AppManager {
|
||||||
return this.getChannelParticipant(id, peerId);
|
return this.getChannelParticipant(id, peerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.getParticipants(id).then((chatParticipants) => {
|
return Promise.resolve(this.getParticipants(id)).then((chatParticipants) => {
|
||||||
assumeType<ChatParticipants.chatParticipants>(chatParticipants);
|
assumeType<ChatParticipants.chatParticipants>(chatParticipants);
|
||||||
const found = chatParticipants.participants.find((chatParticipant) => {
|
const found = chatParticipants.participants.find((chatParticipant) => {
|
||||||
if(getParticipantPeerId(chatParticipant) === peerId) {
|
if(getParticipantPeerId(chatParticipant) === peerId) {
|
||||||
|
@ -360,17 +360,19 @@ export class AppProfileManager extends AppManager {
|
||||||
!(chat as Chat.channel).pFlags.creator &&
|
!(chat as Chat.channel).pFlags.creator &&
|
||||||
!(chat as Chat.channel).admin_rights
|
!(chat as Chat.channel).admin_rights
|
||||||
)) {
|
)) {
|
||||||
return Promise.reject();
|
throw makeError('PEER_ID_INVALID');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.apiManager.invokeApiCacheable('channels.getParticipants', {
|
const result = this.apiManager.invokeApiCacheable('channels.getParticipants', {
|
||||||
channel: this.appChatsManager.getChannelInput(id),
|
channel: this.appChatsManager.getChannelInput(id),
|
||||||
filter,
|
filter,
|
||||||
offset,
|
offset,
|
||||||
limit,
|
limit,
|
||||||
hash: '0'
|
hash: '0'
|
||||||
}, {cacheSeconds: 60}).then((result) => {
|
}, {cacheSeconds: 60, syncIfHasResult: true});
|
||||||
|
|
||||||
|
return callbackify(result, (result) => {
|
||||||
this.appUsersManager.saveApiUsers((result as ChannelsChannelParticipants.channelsChannelParticipants).users);
|
this.appUsersManager.saveApiUsers((result as ChannelsChannelParticipants.channelsChannelParticipants).users);
|
||||||
return result as ChannelsChannelParticipants.channelsChannelParticipants;
|
return result as ChannelsChannelParticipants.channelsChannelParticipants;
|
||||||
});
|
});
|
||||||
|
@ -498,11 +500,11 @@ export class AppProfileManager extends AppManager {
|
||||||
|
|
||||||
let promise: Promise<PeerId[]>;
|
let promise: Promise<PeerId[]>;
|
||||||
if(this.appChatsManager.isChannel(chatId)) {
|
if(this.appChatsManager.isChannel(chatId)) {
|
||||||
promise = this.getChannelParticipants(chatId, {
|
promise = Promise.resolve(this.getChannelParticipants(chatId, {
|
||||||
_: 'channelParticipantsMentions',
|
_: 'channelParticipantsMentions',
|
||||||
q: query,
|
q: query,
|
||||||
top_msg_id: getServerMessageId(threadId)
|
top_msg_id: getServerMessageId(threadId)
|
||||||
}, 50, 0).then((cP) => {
|
}, 50, 0)).then((cP) => {
|
||||||
return cP.participants.map((p) => getParticipantPeerId(p));
|
return cP.participants.map((p) => getParticipantPeerId(p));
|
||||||
});
|
});
|
||||||
} else if(chatId) {
|
} else if(chatId) {
|
||||||
|
|
|
@ -41,6 +41,7 @@ export default abstract class ApiManagerMethods extends AppManager {
|
||||||
timestamp: number,
|
timestamp: number,
|
||||||
promise: Promise<any>,
|
promise: Promise<any>,
|
||||||
fulfilled: boolean,
|
fulfilled: boolean,
|
||||||
|
result?: any,
|
||||||
timeout?: number,
|
timeout?: number,
|
||||||
params: any
|
params: any
|
||||||
}
|
}
|
||||||
|
@ -159,7 +160,7 @@ export default abstract class ApiManagerMethods extends AppManager {
|
||||||
const {method, processResult, processError, params, options} = o;
|
const {method, processResult, processError, params, options} = o;
|
||||||
const cache = this.apiPromisesSingleProcess;
|
const cache = this.apiPromisesSingleProcess;
|
||||||
const cacheKey = options.cacheKey || JSON.stringify(params);
|
const cacheKey = options.cacheKey || JSON.stringify(params);
|
||||||
const map = cache[method] ?? (cache[method] = new Map());
|
const map = cache[method] ??= new Map();
|
||||||
const oldPromise = map.get(cacheKey);
|
const oldPromise = map.get(cacheKey);
|
||||||
if(oldPromise) {
|
if(oldPromise) {
|
||||||
return oldPromise;
|
return oldPromise;
|
||||||
|
@ -201,16 +202,23 @@ export default abstract class ApiManagerMethods extends AppManager {
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
public invokeApiCacheable<T extends keyof MethodDeclMap>(method: T, params: MethodDeclMap[T]['req'] = {} as any, options: InvokeApiOptions & Partial<{cacheSeconds: number, override: boolean}> = {}): Promise<MethodDeclMap[T]['res']> {
|
public invokeApiCacheable<
|
||||||
const cache = this.apiPromisesCacheable[method] ?? (this.apiPromisesCacheable[method] = {});
|
T extends keyof MethodDeclMap,
|
||||||
|
O extends InvokeApiOptions & Partial<{cacheSeconds: number, override: boolean, syncIfHasResult: boolean}>
|
||||||
|
>(
|
||||||
|
method: T,
|
||||||
|
params: MethodDeclMap[T]['req'] = {} as any,
|
||||||
|
options: O = {} as any
|
||||||
|
): O['syncIfHasResult'] extends true ? MethodDeclMap[T]['res'] | Promise<MethodDeclMap[T]['res']> : Promise<MethodDeclMap[T]['res']> {
|
||||||
|
const cache = this.apiPromisesCacheable[method] ??= {};
|
||||||
const queryJSON = JSON.stringify(params);
|
const queryJSON = JSON.stringify(params);
|
||||||
const item = cache[queryJSON];
|
let item = cache[queryJSON];
|
||||||
if(item && (!options.override || !item.fulfilled)) {
|
if(item && (!options.override || !item.fulfilled)) {
|
||||||
return item.promise;
|
return options.syncIfHasResult && item.hasOwnProperty('result') ? item.result : item.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(options.override) {
|
if(options.override) {
|
||||||
if(item && item.timeout) {
|
if(item?.timeout) {
|
||||||
clearTimeout(item.timeout);
|
clearTimeout(item.timeout);
|
||||||
delete item.timeout;
|
delete item.timeout;
|
||||||
}
|
}
|
||||||
|
@ -221,14 +229,22 @@ export default abstract class ApiManagerMethods extends AppManager {
|
||||||
let timeout: number;
|
let timeout: number;
|
||||||
if(options.cacheSeconds) {
|
if(options.cacheSeconds) {
|
||||||
timeout = ctx.setTimeout(() => {
|
timeout = ctx.setTimeout(() => {
|
||||||
delete cache[queryJSON];
|
if(cache[queryJSON] === item) {
|
||||||
|
delete cache[queryJSON];
|
||||||
|
}
|
||||||
}, options.cacheSeconds * 1000);
|
}, options.cacheSeconds * 1000);
|
||||||
delete options.cacheSeconds;
|
delete options.cacheSeconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
const promise = this.invokeApi(method, params, options);
|
const promise = this.invokeApi(method, params, options);
|
||||||
|
|
||||||
cache[queryJSON] = {
|
const onResult = (result: any) => {
|
||||||
|
item.result = result;
|
||||||
|
};
|
||||||
|
|
||||||
|
promise.then(onResult, onResult);
|
||||||
|
|
||||||
|
item = cache[queryJSON] = {
|
||||||
timestamp: Date.now(),
|
timestamp: Date.now(),
|
||||||
fulfilled: false,
|
fulfilled: false,
|
||||||
timeout,
|
timeout,
|
||||||
|
|
|
@ -2115,6 +2115,9 @@ $bubble-border-radius-big: 12px;
|
||||||
// order: 1;
|
// order: 1;
|
||||||
//width: max-content;
|
//width: max-content;
|
||||||
//white-space: nowrap;
|
//white-space: nowrap;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
.badge-fake {
|
.badge-fake {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -2123,6 +2126,14 @@ $bubble-border-radius-big: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&-name-rank {
|
||||||
|
color: var(--message-time-color);
|
||||||
|
font-weight: var(--font-weight-normal);
|
||||||
|
font-size: var(--messages-time-text-size);
|
||||||
|
margin-inline-start: .125rem;
|
||||||
|
@include text-overflow();
|
||||||
|
}
|
||||||
|
|
||||||
/* &:not(.is-group-first) .bubble-content > .name .name {
|
/* &:not(.is-group-first) .bubble-content > .name .name {
|
||||||
display: none;
|
display: none;
|
||||||
} */
|
} */
|
||||||
|
|
Loading…
Reference in New Issue