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 PollElement from '../poll';
|
||||
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 {FocusDirection, ScrollStartCallbackDimensions} from '../../helpers/fastSmoothScroll';
|
||||
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 wrapTopicNameButton from '../wrappers/topicNameButton';
|
||||
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 {copyTextToClipboard} from '../../helpers/clipboard';
|
||||
import liteMode from '../../helpers/liteMode';
|
||||
|
@ -137,6 +134,7 @@ import getMediaDurationFromMessage from '../../lib/appManagers/utils/messages/ge
|
|||
import wrapLocalSticker from '../wrappers/localSticker';
|
||||
import {LottieAssetName} from '../../lib/rlottie/lottieLoader';
|
||||
import clamp from '../../helpers/number/clamp';
|
||||
import getParticipantRank from '../../lib/appManagers/utils/chats/getParticipantRank';
|
||||
|
||||
export const USER_REACTIONS_INLINE = false;
|
||||
const USE_MEDIA_TAILS = false;
|
||||
|
@ -302,6 +300,9 @@ export default class ChatBubbles {
|
|||
|
||||
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>;
|
||||
|
||||
constructor(
|
||||
|
@ -3146,6 +3147,45 @@ export default class ChatBubbles {
|
|||
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 = deferredPromise<void>(); */
|
||||
|
||||
|
@ -5093,14 +5133,16 @@ export default class ChatBubbles {
|
|||
|
||||
const isForward = fwdFromId || fwdFrom;
|
||||
if(isHidden) {
|
||||
// /////this.log('message to render hidden', message);
|
||||
title = document.createElement('span');
|
||||
setInnerHTML(title, wrapEmojiText(fwdFrom.from_name));
|
||||
title.classList.add('peer-title');
|
||||
// title = fwdFrom.from_name;
|
||||
bubble.classList.add('hidden-profile');
|
||||
} 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;
|
||||
|
@ -5218,6 +5260,30 @@ export default class ChatBubbles {
|
|||
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(!attachmentDiv) {
|
||||
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) {
|
||||
const isMounted = !!this.chatInner.parentElement;
|
||||
if(!isMounted) {
|
||||
|
|
|
@ -1104,7 +1104,7 @@ const lang = {
|
|||
'one_value': '%d Comment',
|
||||
'other_value': '%d Comments'
|
||||
},
|
||||
'Chat.TopicBadge': 'topic creator',
|
||||
// 'Chat.TopicBadge': 'topic creator',
|
||||
'ChatTitle.ReportMessages': 'Report Messages',
|
||||
'Chat.Send.WithoutSound': 'Send Without Sound',
|
||||
'Chat.Send.SetReminder': 'Set a Reminder',
|
||||
|
|
|
@ -300,7 +300,7 @@ export class AppProfileManager extends AppManager {
|
|||
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;
|
||||
if(chatParticipants._ !== 'chatParticipants') {
|
||||
throw makeError('CHAT_PRIVATE');
|
||||
|
@ -330,7 +330,7 @@ export class AppProfileManager extends AppManager {
|
|||
return this.getChannelParticipant(id, peerId);
|
||||
}
|
||||
|
||||
return this.getParticipants(id).then((chatParticipants) => {
|
||||
return Promise.resolve(this.getParticipants(id)).then((chatParticipants) => {
|
||||
assumeType<ChatParticipants.chatParticipants>(chatParticipants);
|
||||
const found = chatParticipants.participants.find((chatParticipant) => {
|
||||
if(getParticipantPeerId(chatParticipant) === peerId) {
|
||||
|
@ -360,17 +360,19 @@ export class AppProfileManager extends AppManager {
|
|||
!(chat as Chat.channel).pFlags.creator &&
|
||||
!(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),
|
||||
filter,
|
||||
offset,
|
||||
limit,
|
||||
hash: '0'
|
||||
}, {cacheSeconds: 60}).then((result) => {
|
||||
}, {cacheSeconds: 60, syncIfHasResult: true});
|
||||
|
||||
return callbackify(result, (result) => {
|
||||
this.appUsersManager.saveApiUsers((result as ChannelsChannelParticipants.channelsChannelParticipants).users);
|
||||
return result as ChannelsChannelParticipants.channelsChannelParticipants;
|
||||
});
|
||||
|
@ -498,11 +500,11 @@ export class AppProfileManager extends AppManager {
|
|||
|
||||
let promise: Promise<PeerId[]>;
|
||||
if(this.appChatsManager.isChannel(chatId)) {
|
||||
promise = this.getChannelParticipants(chatId, {
|
||||
promise = Promise.resolve(this.getChannelParticipants(chatId, {
|
||||
_: 'channelParticipantsMentions',
|
||||
q: query,
|
||||
top_msg_id: getServerMessageId(threadId)
|
||||
}, 50, 0).then((cP) => {
|
||||
}, 50, 0)).then((cP) => {
|
||||
return cP.participants.map((p) => getParticipantPeerId(p));
|
||||
});
|
||||
} else if(chatId) {
|
||||
|
|
|
@ -41,6 +41,7 @@ export default abstract class ApiManagerMethods extends AppManager {
|
|||
timestamp: number,
|
||||
promise: Promise<any>,
|
||||
fulfilled: boolean,
|
||||
result?: any,
|
||||
timeout?: number,
|
||||
params: any
|
||||
}
|
||||
|
@ -159,7 +160,7 @@ export default abstract class ApiManagerMethods extends AppManager {
|
|||
const {method, processResult, processError, params, options} = o;
|
||||
const cache = this.apiPromisesSingleProcess;
|
||||
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);
|
||||
if(oldPromise) {
|
||||
return oldPromise;
|
||||
|
@ -201,16 +202,23 @@ export default abstract class ApiManagerMethods extends AppManager {
|
|||
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']> {
|
||||
const cache = this.apiPromisesCacheable[method] ?? (this.apiPromisesCacheable[method] = {});
|
||||
public invokeApiCacheable<
|
||||
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 item = cache[queryJSON];
|
||||
let item = cache[queryJSON];
|
||||
if(item && (!options.override || !item.fulfilled)) {
|
||||
return item.promise;
|
||||
return options.syncIfHasResult && item.hasOwnProperty('result') ? item.result : item.promise;
|
||||
}
|
||||
|
||||
if(options.override) {
|
||||
if(item && item.timeout) {
|
||||
if(item?.timeout) {
|
||||
clearTimeout(item.timeout);
|
||||
delete item.timeout;
|
||||
}
|
||||
|
@ -221,14 +229,22 @@ export default abstract class ApiManagerMethods extends AppManager {
|
|||
let timeout: number;
|
||||
if(options.cacheSeconds) {
|
||||
timeout = ctx.setTimeout(() => {
|
||||
delete cache[queryJSON];
|
||||
if(cache[queryJSON] === item) {
|
||||
delete cache[queryJSON];
|
||||
}
|
||||
}, options.cacheSeconds * 1000);
|
||||
delete options.cacheSeconds;
|
||||
}
|
||||
|
||||
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(),
|
||||
fulfilled: false,
|
||||
timeout,
|
||||
|
|
|
@ -2115,6 +2115,9 @@ $bubble-border-radius-big: 12px;
|
|||
// order: 1;
|
||||
//width: max-content;
|
||||
//white-space: nowrap;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.badge-fake {
|
||||
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 {
|
||||
display: none;
|
||||
} */
|
||||
|
|
Loading…
Reference in New Issue