parent
5fdffa3b18
commit
820885dbd9
|
@ -245,7 +245,7 @@ export class AppMediaPlaybackController extends EventListenerBase<{
|
|||
return media;
|
||||
}
|
||||
|
||||
const doc = getMediaFromMessage(message) as Document.document;
|
||||
const doc = getMediaFromMessage(message, true) as Document.document;
|
||||
storage.set(mid, media = document.createElement(doc.type === 'round' || doc.type === 'video' ? 'video' : 'audio'));
|
||||
// const source = document.createElement('source');
|
||||
// source.type = doc.type === 'voice' && !opusDecodeController.isPlaySupported() ? 'audio/wav' : doc.mime_type;
|
||||
|
@ -431,7 +431,7 @@ export class AppMediaPlaybackController extends EventListenerBase<{
|
|||
|
||||
await onMediaLoad(playingMedia, undefined, false); // have to wait for load, otherwise on macOS won't set
|
||||
|
||||
const doc = getMediaFromMessage(message) as MyDocument;
|
||||
const doc = getMediaFromMessage(message, true) as MyDocument;
|
||||
|
||||
const artwork: MediaImage[] = [];
|
||||
|
||||
|
@ -554,7 +554,7 @@ export class AppMediaPlaybackController extends EventListenerBase<{
|
|||
|
||||
const message = this.getMessageByMedia(playingMedia);
|
||||
return {
|
||||
doc: getMediaFromMessage(message) as MyDocument,
|
||||
doc: getMediaFromMessage(message, true) as MyDocument,
|
||||
message,
|
||||
media: playingMedia,
|
||||
playbackParams: this.getPlaybackParams()
|
||||
|
@ -858,7 +858,7 @@ export class AppMediaPlaybackController extends EventListenerBase<{
|
|||
}
|
||||
|
||||
private getPlaybackMediaTypeFromMessage(message: Message.message) {
|
||||
const doc = getMediaFromMessage(message) as MyDocument;
|
||||
const doc = getMediaFromMessage(message, true) as MyDocument;
|
||||
let mediaType: PlaybackMediaType = 'audio';
|
||||
if(doc?.type) {
|
||||
if(doc.type === 'voice' || doc.type === 'round') {
|
||||
|
|
|
@ -51,7 +51,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
|
|||
processItem: (item) => {
|
||||
const isForDocument = this.searchContext.inputFilter._ === 'inputMessagesFilterDocument';
|
||||
const {mid, peerId} = item;
|
||||
const media: MyPhoto | MyDocument = getMediaFromMessage(item);
|
||||
const media = getMediaFromMessage(item, true);
|
||||
|
||||
if(!media) return;
|
||||
|
||||
|
@ -226,7 +226,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
|
|||
|
||||
onDownloadClick = () => {
|
||||
const {message} = this.target;
|
||||
const media = getMediaFromMessage(message);
|
||||
const media = getMediaFromMessage(message, true);
|
||||
if(!media) return;
|
||||
appDownloadManager.downloadToDisc({media, queueId: appImManager.chat.bubbles.lazyLoadQueue.queueId});
|
||||
};
|
||||
|
@ -258,7 +258,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
|
|||
|
||||
const mid = message.mid;
|
||||
const fromId = (message as Message.message).fwd_from && !message.fromId ? (message as Message.message).fwd_from.from_name : message.fromId;
|
||||
const media = getMediaFromMessage(message);
|
||||
const media = getMediaFromMessage(message, true);
|
||||
|
||||
const noForwards = await this.managers.appPeersManager.noForwards(message.peerId);
|
||||
const isServiceMessage = message._ === 'messageService';
|
||||
|
@ -283,7 +283,7 @@ export default class AppMediaViewer extends AppMediaViewerBase<'caption', 'delet
|
|||
this.wholeDiv.classList.toggle('no-forwards', cantDownloadMessage);
|
||||
|
||||
this.setCaption(message);
|
||||
const promise = super._openMedia(media, message.date, fromId, fromRight, target, reverse, prevTargets, nextTargets, message/* , needLoadMore */);
|
||||
const promise = super._openMedia(media as MyPhoto | MyDocument, message.date, fromId, fromRight, target, reverse, prevTargets, nextTargets, message/* , needLoadMore */);
|
||||
this.target.mid = mid;
|
||||
this.target.peerId = message.peerId;
|
||||
this.target.message = message;
|
||||
|
|
|
@ -42,7 +42,7 @@ import EventListenerBase from '../helpers/eventListenerBase';
|
|||
import {MyMessage} from '../lib/appManagers/appMessagesManager';
|
||||
import {NULL_PEER_ID} from '../lib/mtproto/mtproto_config';
|
||||
import {isFullScreen} from '../helpers/dom/fullScreen';
|
||||
import {attachClickEvent} from '../helpers/dom/clickEvent';
|
||||
import {attachClickEvent, hasMouseMovedSinceDown} from '../helpers/dom/clickEvent';
|
||||
import SearchListLoader from '../helpers/searchListLoader';
|
||||
import createVideo from '../helpers/dom/createVideo';
|
||||
import {AppManagers} from '../lib/appManagers/managers';
|
||||
|
@ -57,6 +57,7 @@ import {toastNew} from './toast';
|
|||
import clamp from '../helpers/number/clamp';
|
||||
import debounce from '../helpers/schedulers/debounce';
|
||||
import isBetween from '../helpers/number/isBetween';
|
||||
import findUpAsChild from '../helpers/dom/findUpAsChild';
|
||||
|
||||
const ZOOM_STEP = 0.5;
|
||||
const ZOOM_INITIAL_VALUE = 1;
|
||||
|
@ -137,6 +138,7 @@ export default class AppMediaViewerBase<
|
|||
protected lastDragDelta: {x: number, y: number} = this.transform;
|
||||
protected lastGestureTime: number;
|
||||
protected clampZoomDebounced: ReturnType<typeof debounce<() => void>>;
|
||||
ignoreNextClick: boolean;
|
||||
|
||||
get target() {
|
||||
return this.listLoader.current;
|
||||
|
@ -228,13 +230,25 @@ export default class AppMediaViewerBase<
|
|||
}, ZOOM_INITIAL_VALUE);
|
||||
this.zoomElements.rangeSelector.setListeners();
|
||||
this.zoomElements.rangeSelector.setHandlers({
|
||||
onScrub: this.setZoomValue,
|
||||
onMouseUp: () => this.setZoomValue()
|
||||
onScrub: (value) => {
|
||||
const add = value - this.transform.scale;
|
||||
this.addZoom(add);
|
||||
this.clampZoomDebounced?.clearTimeout();
|
||||
},
|
||||
onMouseDown: () => {
|
||||
this.moversContainer.classList.add('no-transition');
|
||||
this.zoomElements.rangeSelector.container.classList.remove('with-transition');
|
||||
},
|
||||
onMouseUp: () => {
|
||||
this.moversContainer.classList.remove('no-transition');
|
||||
this.zoomElements.rangeSelector.container.classList.add('with-transition');
|
||||
this.setZoomValue();
|
||||
}
|
||||
});
|
||||
|
||||
this.zoomElements.container.append(this.zoomElements.btnOut, this.zoomElements.rangeSelector.container, this.zoomElements.btnIn);
|
||||
|
||||
if(!IS_TOUCH_SUPPORTED && false) {
|
||||
if(!IS_TOUCH_SUPPORTED) {
|
||||
this.wholeDiv.append(this.zoomElements.container);
|
||||
}
|
||||
|
||||
|
@ -349,7 +363,7 @@ export default class AppMediaViewerBase<
|
|||
this.swipeHandler = new SwipeHandler({
|
||||
element: this.wholeDiv,
|
||||
onReset: this.onSwipeReset,
|
||||
onFirstSwipe: this.onSwipeFirst,
|
||||
onFirstSwipe: this.onSwipeFirst as any,
|
||||
onSwipe: (xDiff, yDiff, e, cancelDrag) => {
|
||||
if(isFullScreen()) {
|
||||
return;
|
||||
|
@ -400,6 +414,7 @@ export default class AppMediaViewerBase<
|
|||
verifyTouchTarget: (e) => {
|
||||
// * Fix for seek input
|
||||
if(isFullScreen() ||
|
||||
findUpAsChild(e.target as HTMLElement, this.zoomElements.container) ||
|
||||
findUpClassName(e.target, 'ckin__controls') ||
|
||||
findUpClassName(e.target, 'media-viewer-caption') ||
|
||||
(findUpClassName(e.target, 'media-viewer-topbar') && e.type !== 'wheel')) {
|
||||
|
@ -413,10 +428,13 @@ export default class AppMediaViewerBase<
|
|||
});
|
||||
}
|
||||
|
||||
protected onSwipeFirst = () => {
|
||||
protected onSwipeFirst = (e: MouseEvent | TouchEvent | WheelEvent) => {
|
||||
this.lastDragOffset = this.lastDragDelta = {x: 0, y: 0};
|
||||
this.lastTransform = {...this.transform};
|
||||
this.moversContainer.classList.add('no-transition');
|
||||
if(e.type !== 'wheel' || !this.ctrlKeyDown) { // keep transition for real mouse wheel
|
||||
this.moversContainer.classList.add('no-transition');
|
||||
this.zoomElements.rangeSelector.container.classList.remove('with-transition');
|
||||
}
|
||||
this.isGesturingNow = true;
|
||||
this.lastGestureTime = Date.now();
|
||||
this.clampZoomDebounced.clearTimeout();
|
||||
|
@ -426,11 +444,16 @@ export default class AppMediaViewerBase<
|
|||
}
|
||||
};
|
||||
|
||||
protected onSwipeReset = () => {
|
||||
protected onSwipeReset = (e?: Event) => {
|
||||
// move
|
||||
this.moversContainer.classList.remove('no-transition');
|
||||
this.zoomElements.rangeSelector.container.classList.add('with-transition');
|
||||
this.clampZoomDebounced.clearTimeout();
|
||||
|
||||
if(e?.type === 'mouseup' && this.draggingType === 'mousemove') {
|
||||
this.ignoreNextClick = true;
|
||||
}
|
||||
|
||||
const {draggingType} = this;
|
||||
this.isZoomingNow = false;
|
||||
this.isGesturingNow = false;
|
||||
|
@ -492,7 +515,7 @@ export default class AppMediaViewerBase<
|
|||
this.isZoomingNow = true;
|
||||
|
||||
const zoomMaxBounceValue = ZOOM_MAX_VALUE * 3;
|
||||
const scale = zoomAdd ? clamp(this.lastTransform.scale + zoomAdd, ZOOM_MIN_VALUE, zoomMaxBounceValue) : (zoom ?? clamp(this.lastTransform.scale * zoomFactor, ZOOM_MIN_VALUE, zoomMaxBounceValue));
|
||||
const scale = zoomAdd !== undefined ? clamp(this.lastTransform.scale + zoomAdd, ZOOM_MIN_VALUE, zoomMaxBounceValue) : (zoom ?? clamp(this.lastTransform.scale * zoomFactor, ZOOM_MIN_VALUE, zoomMaxBounceValue));
|
||||
const scaleFactor = scale / this.lastTransform.scale;
|
||||
const offsetX = Math.abs(Math.min(this.lastTransform.x, 0));
|
||||
const offsetY = Math.abs(Math.min(this.lastTransform.y, 0));
|
||||
|
@ -661,8 +684,8 @@ export default class AppMediaViewerBase<
|
|||
|
||||
this.moversContainer.style.transform = `translate3d(${this.transform.x.toFixed(3)}px, ${this.transform.y.toFixed(3)}px, 0px) scale(${value.toFixed(3)})`;
|
||||
|
||||
this.zoomElements.btnOut.classList.toggle('inactive', value === ZOOM_MIN_VALUE);
|
||||
this.zoomElements.btnIn.classList.toggle('inactive', value === ZOOM_MAX_VALUE);
|
||||
this.zoomElements.btnOut.classList.toggle('inactive', value <= ZOOM_MIN_VALUE);
|
||||
this.zoomElements.btnIn.classList.toggle('inactive', value >= ZOOM_MAX_VALUE);
|
||||
|
||||
this.toggleZoom(value !== ZOOM_INITIAL_VALUE);
|
||||
};
|
||||
|
@ -735,6 +758,11 @@ export default class AppMediaViewerBase<
|
|||
}
|
||||
|
||||
onClick = (e: MouseEvent) => {
|
||||
if(this.ignoreNextClick) {
|
||||
this.ignoreNextClick = undefined;
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.setMoverAnimationPromise) return;
|
||||
|
||||
const target = e.target as HTMLElement;
|
||||
|
@ -756,7 +784,11 @@ export default class AppMediaViewerBase<
|
|||
return;
|
||||
}
|
||||
|
||||
const isZooming = this.isZooming;
|
||||
if(hasMouseMovedSinceDown(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isZooming = this.isZooming && false;
|
||||
let mover: HTMLElement = null;
|
||||
const classNames = ['ckin__player', 'media-viewer-buttons', 'media-viewer-author', 'media-viewer-caption', 'zoom-container'];
|
||||
if(isZooming) {
|
||||
|
@ -1850,8 +1882,9 @@ export default class AppMediaViewerBase<
|
|||
const cancellablePromise = isDocument ? appDownloadManager.downloadMediaURL({media}) : appDownloadManager.downloadMediaURL({media, thumb: size});
|
||||
|
||||
const photoSizes = !isDocument && media.sizes.slice().filter((size) => (size as PhotoSize.photoSize).w) as PhotoSize.photoSize[];
|
||||
photoSizes?.sort((a, b) => b.size - a.size);
|
||||
const cancellableFullPromise = !isDocument && appDownloadManager.downloadMediaURL({media, thumb: photoSizes?.[0]});
|
||||
photoSizes && photoSizes.sort((a, b) => b.size - a.size);
|
||||
const fullPhotoSize = photoSizes?.[0];
|
||||
const cancellableFullPromise = !isDocument && fullPhotoSize !== size && appDownloadManager.downloadMediaURL({media, thumb: fullPhotoSize});
|
||||
|
||||
onAnimationEnd.then(async() => {
|
||||
if(!(await getCacheContext()).url) {
|
||||
|
@ -1866,8 +1899,6 @@ export default class AppMediaViewerBase<
|
|||
return;
|
||||
}
|
||||
|
||||
// /////this.log('indochina', blob);
|
||||
|
||||
const url = (await getCacheContext()).url;
|
||||
if(target instanceof SVGSVGElement) {
|
||||
this.updateMediaSource(target, url, 'img');
|
||||
|
@ -1886,8 +1917,6 @@ export default class AppMediaViewerBase<
|
|||
const image = new Image();
|
||||
image.classList.add('thumbnail');
|
||||
|
||||
// this.log('will renderImageFromUrl:', image, div, target);
|
||||
|
||||
renderImageFromUrl(image, url, () => {
|
||||
fastRaf(() => {
|
||||
this.updateMediaSource(target, url, 'img');
|
||||
|
@ -1902,7 +1931,7 @@ export default class AppMediaViewerBase<
|
|||
});
|
||||
}, false);
|
||||
|
||||
cancellableFullPromise?.then((url) => {
|
||||
cancellableFullPromise && cancellableFullPromise.then((url) => {
|
||||
const fullImage = new Image();
|
||||
fullImage.classList.add('thumbnail');
|
||||
renderImageFromUrl(fullImage, url, () => {
|
||||
|
|
|
@ -648,7 +648,7 @@ export default class AppSearchSuper {
|
|||
}
|
||||
|
||||
private async processPhotoVideoFilter({message, promises, middleware}: ProcessSearchSuperResult) {
|
||||
const media = getMediaFromMessage(message);
|
||||
const media = getMediaFromMessage(message, true);
|
||||
|
||||
const div = document.createElement('div');
|
||||
div.classList.add('grid-item');
|
||||
|
@ -711,7 +711,7 @@ export default class AppSearchSuper {
|
|||
}
|
||||
|
||||
private async processDocumentFilter({message, inputFilter}: ProcessSearchSuperResult) {
|
||||
const document = getMediaFromMessage(message) as Document.document;
|
||||
const document = getMediaFromMessage(message, true) as Document.document;
|
||||
const showSender = this.showSender || (['voice', 'round'] as MyDocument['type'][]).includes(document.type);
|
||||
|
||||
const div = await wrapDocument({
|
||||
|
|
|
@ -31,7 +31,7 @@ import filterAsync from '../helpers/array/filterAsync';
|
|||
import getParticipantPeerId from '../lib/appManagers/utils/chats/getParticipantPeerId';
|
||||
import getChatMembersString from './wrappers/getChatMembersString';
|
||||
import getUserStatusString from './wrappers/getUserStatusString';
|
||||
import {Chat, User} from '../layer';
|
||||
import {ChannelsChannelParticipants, Chat, User} from '../layer';
|
||||
import canSendToUser from '../lib/appManagers/utils/users/canSendToUser';
|
||||
import hasRights from '../lib/appManagers/utils/chats/hasRights';
|
||||
import getDialogIndex from '../lib/appManagers/utils/dialogs/getDialogIndex';
|
||||
|
@ -75,7 +75,7 @@ export default class AppSelectPeers {
|
|||
private onChange: (length: number) => void;
|
||||
private peerType: SelectSearchPeerType[] = ['dialogs'];
|
||||
private renderResultsFunc: (peerIds: PeerId[]) => void | Promise<void>;
|
||||
private chatRightsAction: ChatRights;
|
||||
private chatRightsActions: ChatRights[];
|
||||
private multiSelect = true;
|
||||
private rippleEnabled = true;
|
||||
private avatarSize: DialogElementSize = 'abitbigger';
|
||||
|
@ -104,7 +104,7 @@ export default class AppSelectPeers {
|
|||
peerId?: AppSelectPeers['peerId'],
|
||||
onFirstRender?: () => void,
|
||||
renderResultsFunc?: AppSelectPeers['renderResultsFunc'],
|
||||
chatRightsAction?: AppSelectPeers['chatRightsAction'],
|
||||
chatRightsActions?: AppSelectPeers['chatRightsActions'],
|
||||
multiSelect?: AppSelectPeers['multiSelect'],
|
||||
rippleEnabled?: AppSelectPeers['rippleEnabled'],
|
||||
avatarSize?: AppSelectPeers['avatarSize'],
|
||||
|
@ -347,7 +347,7 @@ export default class AppSelectPeers {
|
|||
dialogs = dialogs.slice();
|
||||
findAndSplice(dialogs, d => d.peerId === rootScope.myId); // no my account
|
||||
|
||||
if(this.chatRightsAction) {
|
||||
if(this.chatRightsActions) {
|
||||
dialogs = await filterAsync(dialogs, (d) => this.filterByRights(d.peerId));
|
||||
if(!middleware()) {
|
||||
return;
|
||||
|
@ -389,8 +389,8 @@ export default class AppSelectPeers {
|
|||
private async filterByRights(peerId: PeerId) {
|
||||
const peer: User | Chat = await this.managers.appPeersManager.getPeer(peerId);
|
||||
if(peerId.isUser()) {
|
||||
return this.chatRightsAction !== 'send_messages' || canSendToUser(peer as User.user);
|
||||
} else if(hasRights(peer as Chat.chat, this.chatRightsAction)) {
|
||||
return this.chatRightsActions[0] !== 'send_plain' || canSendToUser(peer as User.user);
|
||||
} else if(this.chatRightsActions.every((action) => hasRights(peer as Chat.chat, action))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -433,7 +433,7 @@ export default class AppSelectPeers {
|
|||
// do not add global result if only dialogs needed
|
||||
let resultPeerIds = isGlobalSearch ? searchResult.my_results.concat(searchResult.results) : searchResult.my_results;
|
||||
|
||||
if(this.chatRightsAction) {
|
||||
if(this.chatRightsActions) {
|
||||
resultPeerIds = await filterAsync(resultPeerIds, (peerId) => this.filterByRights(peerId));
|
||||
if(!middleware()) {
|
||||
return;
|
||||
|
@ -474,7 +474,7 @@ export default class AppSelectPeers {
|
|||
const pageCount = 50; // same as in group permissions to use cache
|
||||
|
||||
const {middleware} = this.getTempId('channelParticipants');
|
||||
const promise = this.managers.appProfileManager.getChannelParticipants(
|
||||
const promise = this.managers.appProfileManager.getParticipants(
|
||||
this.peerId.toChatId(),
|
||||
{
|
||||
_: 'channelParticipantsSearch',
|
||||
|
@ -492,18 +492,20 @@ export default class AppSelectPeers {
|
|||
this.loadedWhat.channelParticipants = true;
|
||||
});
|
||||
|
||||
const participants = await promise;
|
||||
const chatParticipants = await promise;
|
||||
if(!middleware()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const peerIds = participants.participants.map((participant) => {
|
||||
return getParticipantPeerId(participant);
|
||||
});
|
||||
const {participants} = chatParticipants;
|
||||
|
||||
const peerIds = participants.map((participant) => getParticipantPeerId(participant));
|
||||
indexOfAndSplice(peerIds, rootScope.myId);
|
||||
this.renderResultsFunc(peerIds);
|
||||
|
||||
if(this.list.childElementCount >= participants.count || participants.participants.length < pageCount) {
|
||||
const count = (chatParticipants as ChannelsChannelParticipants.channelsChannelParticipants).count ?? participants.length;
|
||||
|
||||
if(this.list.childElementCount >= count || participants.length < pageCount) {
|
||||
this.loadedWhat.channelParticipants = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ function createWaveformBars(waveform: Uint8Array, duration: number) {
|
|||
const bar_value = Math.max(((maxValue * maxDelta) + ((normValue + 1) / 2)) / (normValue + 1), barHeightMin);
|
||||
|
||||
const h = `
|
||||
<rect x="${barX}" y="${barHeightMax - bar_value}" width="${barWidth}" height="${bar_value}" rx="1" ry="1"></rect>
|
||||
<rect class="audio-waveform-bar" x="${barX}" y="${barHeightMax - bar_value}" width="${barWidth}" height="${bar_value}" rx="1" ry="1"></rect>
|
||||
`;
|
||||
html += h;
|
||||
|
||||
|
@ -197,6 +197,11 @@ async function wrapVoiceMessage(audioEl: AudioElement) {
|
|||
// TODO: State to enum
|
||||
audioEl.transcriptionState = 2;
|
||||
} else {
|
||||
const message = audioEl.message;
|
||||
if(message.pFlags.is_outgoing) {
|
||||
return;
|
||||
}
|
||||
|
||||
audioEl.transcriptionState = 1;
|
||||
!speechRecognitionLoader.parentElement && speechRecognitionDiv.append(speechRecognitionLoader);
|
||||
doubleRaf().then(() => {
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import contextMenuController from '../helpers/contextMenuController';
|
||||
import cancelEvent from '../helpers/dom/cancelEvent';
|
||||
import {AttachClickOptions, CLICK_EVENT_NAME} from '../helpers/dom/clickEvent';
|
||||
import {AttachClickOptions, CLICK_EVENT_NAME, hasMouseMovedSinceDown} from '../helpers/dom/clickEvent';
|
||||
import ListenerSetter from '../helpers/listenerSetter';
|
||||
import ButtonIcon from './buttonIcon';
|
||||
import ButtonMenu, {ButtonMenuItemOptionsVerifiable} from './buttonMenu';
|
||||
|
@ -28,7 +28,7 @@ export function ButtonMenuToggleHandler({
|
|||
const add = options?.listenerSetter ? options.listenerSetter.add(el) : el.addEventListener.bind(el);
|
||||
|
||||
add(CLICK_EVENT_NAME, (e: Event) => {
|
||||
if(!el.classList.contains('btn-menu-toggle')) return false;
|
||||
if(!el.classList.contains('btn-menu-toggle') || hasMouseMovedSinceDown(e)) return false;
|
||||
|
||||
cancelEvent(e);
|
||||
|
||||
|
|
|
@ -656,11 +656,11 @@ export default class ChatBubbles {
|
|||
dots?.remove();
|
||||
}
|
||||
|
||||
if(!text && !pending && !transcribedText.classList.contains('has-some-text')) {
|
||||
transcribedText.append(i18n('Chat.Voice.Transribe.Error'));
|
||||
if(!text && !pending/* && !transcribedText.classList.contains('has-some-text') */) {
|
||||
transcribedText.replaceChildren(i18n('Chat.Voice.Transribe.Error'));
|
||||
transcribedText.classList.add('is-error');
|
||||
} else {
|
||||
transcribedText.classList.add('has-some-text');
|
||||
} else if(text) {
|
||||
// transcribedText.classList.add('has-some-text');
|
||||
transcribedText.firstChild.textContent = text;
|
||||
}
|
||||
|
||||
|
@ -1101,9 +1101,14 @@ export default class ChatBubbles {
|
|||
|
||||
const chat = await this.managers.appChatsManager.getChat(chatId);
|
||||
const hadRights = this.chatInner.classList.contains('has-rights');
|
||||
const hasRights = await this.chat.canSend();
|
||||
const hadPlainRights = this.chat.input.canSendPlain();
|
||||
const [hasRights, hasPlainRights, canEmbedLinks] = await Promise.all([
|
||||
this.chat.canSend('send_messages'),
|
||||
this.chat.canSend('send_plain'),
|
||||
this.chat.canSend('embed_links')
|
||||
]);
|
||||
|
||||
if(hadRights !== hasRights) {
|
||||
if(hadRights !== hasRights || hadPlainRights !== hasPlainRights) {
|
||||
const callbacks = await Promise.all([
|
||||
this.finishPeerChange(),
|
||||
this.chat.input.finishPeerChange()
|
||||
|
@ -1112,6 +1117,12 @@ export default class ChatBubbles {
|
|||
callbacks.forEach((callback) => callback());
|
||||
}
|
||||
|
||||
// reset webpage
|
||||
if((canEmbedLinks && !this.chat.input.willSendWebPage) || (!canEmbedLinks && this.chat.input.willSendWebPage)) {
|
||||
this.chat.input.lastUrl = '';
|
||||
this.chat.input.onMessageInput();
|
||||
}
|
||||
|
||||
if(!!(chat as MTChat.channel).pFlags.forum !== this.chat.isForum && this.chat.type === 'chat') {
|
||||
this.chat.peerId = 0;
|
||||
this.chat.appImManager.setPeer({peerId});
|
||||
|
|
|
@ -19,7 +19,7 @@ import findUpClassName from '../../helpers/dom/findUpClassName';
|
|||
import cancelEvent from '../../helpers/dom/cancelEvent';
|
||||
import {attachClickEvent, simulateClickEvent} from '../../helpers/dom/clickEvent';
|
||||
import isSelectionEmpty from '../../helpers/dom/isSelectionEmpty';
|
||||
import {Message, Poll, Chat as MTChat, MessageMedia, AvailableReaction, MessageEntity, InputStickerSet, StickerSet, Document, Reaction} from '../../layer';
|
||||
import {Message, Poll, Chat as MTChat, MessageMedia, AvailableReaction, MessageEntity, InputStickerSet, StickerSet, Document, Reaction, Photo} from '../../layer';
|
||||
import PopupReportMessages from '../popups/reportMessages';
|
||||
import assumeType from '../../helpers/assumeType';
|
||||
import PopupSponsored from '../popups/sponsored';
|
||||
|
@ -39,7 +39,7 @@ import {attachContextMenuListener} from '../../helpers/dom/attachContextMenuList
|
|||
import filterAsync from '../../helpers/array/filterAsync';
|
||||
import appDownloadManager from '../../lib/appManagers/appDownloadManager';
|
||||
import {SERVICE_PEER_ID} from '../../lib/mtproto/mtproto_config';
|
||||
import {MessagesStorageKey} from '../../lib/appManagers/appMessagesManager';
|
||||
import {MessagesStorageKey, MyMessage} from '../../lib/appManagers/appMessagesManager';
|
||||
import filterUnique from '../../helpers/array/filterUnique';
|
||||
import replaceContent from '../../helpers/dom/replaceContent';
|
||||
import wrapEmojiText from '../../lib/richTextProcessor/wrapEmojiText';
|
||||
|
@ -79,6 +79,7 @@ export default class ChatContextMenu {
|
|||
private albumMessages: Message.message[];
|
||||
private linkToMessage: Awaited<ReturnType<ChatContextMenu['getUrlToMessage']>>;
|
||||
private selectedMessagesText: string;
|
||||
private selectedMessages: MyMessage[];
|
||||
|
||||
constructor(
|
||||
private chat: Chat,
|
||||
|
@ -199,6 +200,7 @@ export default class ChatContextMenu {
|
|||
this.canOpenReactedList = undefined;
|
||||
this.linkToMessage = await this.getUrlToMessage();
|
||||
this.selectedMessagesText = await this.getSelectedMessagesText();
|
||||
this.selectedMessages = this.chat.selection.isSelecting ? await this.chat.selection.getSelectedMessages() : undefined;
|
||||
|
||||
const initResult = await this.init();
|
||||
if(!initResult) {
|
||||
|
@ -445,31 +447,9 @@ export default class ChatContextMenu {
|
|||
icon: 'download',
|
||||
text: 'MediaViewer.Context.Download',
|
||||
onClick: () => {
|
||||
appDownloadManager.downloadToDisc({media: getMediaFromMessage(this.message)});
|
||||
appDownloadManager.downloadToDisc({media: getMediaFromMessage(this.message, true)});
|
||||
},
|
||||
verify: () => {
|
||||
if(!canSaveMessageMedia(this.message) || this.noForwards) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isPhoto: boolean = !!((this.message as Message.message).media as MessageMedia.messageMediaPhoto)?.photo;
|
||||
let isGoodType = false
|
||||
|
||||
if(isPhoto) {
|
||||
isGoodType = true;
|
||||
} else {
|
||||
const doc: MyDocument = ((this.message as Message.message).media as MessageMedia.messageMediaDocument)?.document as any;
|
||||
if(!doc) return false;
|
||||
// isGoodType = doc.type && (['gif', 'video', 'audio', 'voice', 'sticker'] as MyDocument['type'][]).includes(doc.type)
|
||||
isGoodType = true;
|
||||
}
|
||||
|
||||
let hasTarget = !!IS_TOUCH_SUPPORTED;
|
||||
|
||||
if(isGoodType) hasTarget ||= !!findUpClassName(this.target, 'document') || !!findUpClassName(this.target, 'audio') || !!findUpClassName(this.target, 'media-sticker-wrapper') || !!findUpClassName(this.target, 'media-photo') || !!findUpClassName(this.target, 'media-video');
|
||||
|
||||
return isGoodType && hasTarget;
|
||||
}
|
||||
verify: () => this.canDownload(this.message, true)
|
||||
}, {
|
||||
icon: 'checkretract',
|
||||
text: 'Chat.Poll.Unvote',
|
||||
|
@ -502,6 +482,16 @@ export default class ChatContextMenu {
|
|||
!this.chat.selection.selectionForwardBtn.hasAttribute('disabled'),
|
||||
notDirect: () => true,
|
||||
withSelection: true
|
||||
}, {
|
||||
icon: 'download',
|
||||
text: 'Message.Context.Selection.Download',
|
||||
onClick: () => {
|
||||
this.selectedMessages.forEach((message) => {
|
||||
appDownloadManager.downloadToDisc({media: getMediaFromMessage(message, true)});
|
||||
});
|
||||
},
|
||||
verify: () => this.selectedMessages ? this.selectedMessages.some((message) => this.canDownload(message, false)) : false,
|
||||
withSelection: true
|
||||
}, {
|
||||
icon: 'flag',
|
||||
text: 'ReportChat',
|
||||
|
@ -574,6 +564,30 @@ export default class ChatContextMenu {
|
|||
}];
|
||||
}
|
||||
|
||||
private canDownload(message: MyMessage, withTarget?: boolean) {
|
||||
if(!canSaveMessageMedia(message) || this.noForwards) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const isPhoto: boolean = !!((message as Message.message).media as MessageMedia.messageMediaPhoto)?.photo;
|
||||
let isGoodType = false
|
||||
|
||||
if(isPhoto) {
|
||||
isGoodType = true;
|
||||
} else {
|
||||
const doc: MyDocument = ((message as Message.message).media as MessageMedia.messageMediaDocument)?.document as any;
|
||||
if(!doc) return false;
|
||||
// isGoodType = doc.type && (['gif', 'video', 'audio', 'voice', 'sticker'] as MyDocument['type'][]).includes(doc.type)
|
||||
isGoodType = true;
|
||||
}
|
||||
|
||||
let hasTarget = !withTarget || !!IS_TOUCH_SUPPORTED;
|
||||
|
||||
if(isGoodType) hasTarget ||= !!findUpClassName(this.target, 'document') || !!findUpClassName(this.target, 'audio') || !!findUpClassName(this.target, 'media-sticker-wrapper') || !!findUpClassName(this.target, 'media-photo') || !!findUpClassName(this.target, 'media-video');
|
||||
|
||||
return isGoodType && hasTarget;
|
||||
}
|
||||
|
||||
private getMessageWithText() {
|
||||
return (this.albumMessages && getAlbumText(this.albumMessages)) || this.message;
|
||||
}
|
||||
|
@ -932,7 +946,7 @@ export default class ChatContextMenu {
|
|||
} else {
|
||||
const peerId = this.peerId;
|
||||
const mids = this.isTargetAGroupedItem ? [this.mid] : await this.chat.getMidsByMid(this.mid);
|
||||
new PopupForward({
|
||||
PopupForward.create({
|
||||
[peerId]: mids
|
||||
});
|
||||
}
|
||||
|
|
|
@ -29,6 +29,8 @@ import generateQId from '../../lib/appManagers/utils/inlineBots/generateQId';
|
|||
import appDownloadManager from '../../lib/appManagers/appDownloadManager';
|
||||
import {AnimationItemGroup} from '../animationIntersector';
|
||||
import wrapPhoto from '../wrappers/photo';
|
||||
import {i18n} from '../../lib/langPack';
|
||||
import {POSTING_NOT_ALLOWED_MAP} from './input';
|
||||
|
||||
const ANIMATION_GROUP: AnimationItemGroup = 'INLINE-HELPER';
|
||||
// const GRID_ITEMS = 5;
|
||||
|
@ -39,7 +41,7 @@ export default class InlineHelper extends AutocompleteHelper {
|
|||
private gifsMasonry: GifsMasonry;
|
||||
private superStickerRenderer: SuperStickerRenderer;
|
||||
private onChangeScreen: () => void;
|
||||
public checkQuery: (peerId: PeerId, username: string, query: string) => ReturnType<InlineHelper['_checkQuery']>;
|
||||
public checkQuery: ReturnType<typeof debounce<InlineHelper['_checkQuery']>>;
|
||||
|
||||
constructor(
|
||||
appendTo: HTMLElement,
|
||||
|
@ -85,7 +87,7 @@ export default class InlineHelper extends AutocompleteHelper {
|
|||
});
|
||||
}
|
||||
|
||||
public _checkQuery = async(peerId: PeerId, username: string, query: string) => {
|
||||
public _checkQuery = async(peerId: PeerId, username: string, query: string, canSendInline: boolean) => {
|
||||
const middleware = this.controller.getMiddleware();
|
||||
|
||||
const peer = await this.managers.appUsersManager.resolveUsername(username);
|
||||
|
@ -97,6 +99,21 @@ export default class InlineHelper extends AutocompleteHelper {
|
|||
throw 'NOT_A_BOT';
|
||||
}
|
||||
|
||||
if(!canSendInline) {
|
||||
if(!middleware()) {
|
||||
throw 'PEER_CHANGED';
|
||||
}
|
||||
|
||||
if(this.init) {
|
||||
this.init();
|
||||
this.init = null;
|
||||
}
|
||||
|
||||
this.container.classList.add('cant-send');
|
||||
this.toggle(false);
|
||||
throw 'NO_INLINES';
|
||||
}
|
||||
|
||||
const renderPromise = this.managers.appInlineBotsManager.getInlineResults(peerId, peer.id, query).then((botResults) => {
|
||||
if(!middleware()) {
|
||||
throw 'PEER_CHANGED';
|
||||
|
@ -252,10 +269,9 @@ export default class InlineHelper extends AutocompleteHelper {
|
|||
parent.append(btnSwitchToPM);
|
||||
}
|
||||
parent.append(this.list = list);
|
||||
this.container.classList.remove('cant-send');
|
||||
|
||||
if(this.gifsMasonry) {
|
||||
this.gifsMasonry.detach();
|
||||
}
|
||||
this.gifsMasonry?.detach();
|
||||
this.gifsMasonry = gifsMasonry;
|
||||
gifsMasonry.attach();
|
||||
|
||||
|
@ -290,5 +306,9 @@ export default class InlineHelper extends AutocompleteHelper {
|
|||
this.scrollable = new Scrollable(this.container);
|
||||
this.lazyLoadQueue = new LazyLoadQueue();
|
||||
this.superStickerRenderer = new SuperStickerRenderer(this.lazyLoadQueue, ANIMATION_GROUP, this.managers);
|
||||
|
||||
const span = i18n(POSTING_NOT_ALLOWED_MAP['send_inline']);
|
||||
span.classList.add('inline-helper-cant-send');
|
||||
this.container.append(span);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -108,9 +108,21 @@ import getAttachMenuBotIcon from '../../lib/appManagers/utils/attachMenuBots/get
|
|||
import TelegramWebView from '../telegramWebView';
|
||||
import forEachReverse from '../../helpers/array/forEachReverse';
|
||||
import {MARKDOWN_ENTITIES} from '../../lib/richTextProcessor';
|
||||
import IMAGE_MIME_TYPES_SUPPORTED from '../../environment/imageMimeTypesSupport';
|
||||
import VIDEO_MIME_TYPES_SUPPORTED from '../../environment/videoMimeTypesSupport';
|
||||
import {ChatRights} from '../../lib/appManagers/appChatsManager';
|
||||
|
||||
const RECORD_MIN_TIME = 500;
|
||||
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
|
||||
|
||||
export const POSTING_NOT_ALLOWED_MAP: {[action in ChatRights]?: LangPackKey} = {
|
||||
send_voices: 'GlobalAttachVoiceRestricted',
|
||||
send_stickers: 'GlobalAttachStickersRestricted',
|
||||
send_gifs: 'GlobalAttachGifRestricted',
|
||||
send_media: 'GlobalAttachMediaRestricted',
|
||||
send_plain: 'GlobalSendMessageRestricted',
|
||||
send_polls: 'ErrorSendRestrictedPollsAll',
|
||||
send_inline: 'GlobalAttachInlineRestricted'
|
||||
};
|
||||
|
||||
type ChatInputHelperType = 'edit' | 'webpage' | 'forward' | 'reply';
|
||||
|
||||
|
@ -123,7 +135,7 @@ export default class ChatInput {
|
|||
private inputMessageContainer: HTMLDivElement;
|
||||
private btnSend: HTMLButtonElement;
|
||||
private btnCancelRecord: HTMLButtonElement;
|
||||
private lastUrl = '';
|
||||
public lastUrl = '';
|
||||
private lastTimeType = 0;
|
||||
|
||||
public chatInput: HTMLElement;
|
||||
|
@ -160,7 +172,7 @@ export default class ChatInput {
|
|||
private forwardWasDroppingAuthor: boolean;
|
||||
|
||||
private getWebPagePromise: Promise<void>;
|
||||
private willSendWebPage: WebPage = null;
|
||||
public willSendWebPage: WebPage = null;
|
||||
private forwarding: {[fromPeerId: PeerId]: number[]};
|
||||
public replyToMsgId: number;
|
||||
public editMsgId: number;
|
||||
|
@ -585,18 +597,37 @@ export default class ChatInput {
|
|||
icon.classList.remove('state-back');
|
||||
});
|
||||
|
||||
// const getSendMediaRights = () => Promise.all([this.chat.canSend('send_photos'), this.chat.canSend('send_videos')]).then(([photos, videos]) => ({photos, videos}));
|
||||
|
||||
const onAttachMediaClick = (photos: boolean, videos: boolean) => {
|
||||
this.fileInput.value = '';
|
||||
|
||||
const accept = [
|
||||
...(photos ? IMAGE_MIME_TYPES_SUPPORTED : []),
|
||||
...(videos ? VIDEO_MIME_TYPES_SUPPORTED : [])
|
||||
].join(', ');
|
||||
|
||||
this.fileInput.setAttribute('accept', accept);
|
||||
this.willAttachType = 'media';
|
||||
this.fileInput.click();
|
||||
};
|
||||
|
||||
this.attachMenuButtons = [{
|
||||
icon: 'image',
|
||||
text: 'Chat.Input.Attach.PhotoOrVideo',
|
||||
onClick: () => {
|
||||
this.fileInput.value = '';
|
||||
const accept = [...MEDIA_MIME_TYPES_SUPPORTED].join(', ');
|
||||
this.fileInput.setAttribute('accept', accept);
|
||||
this.willAttachType = 'media';
|
||||
this.fileInput.click();
|
||||
},
|
||||
verify: () => this.chat.canSend('send_media')
|
||||
onClick: () => onAttachMediaClick(true, true)
|
||||
// verify: () => getSendMediaRights().then(({photos, videos}) => photos && videos)
|
||||
}, /* {
|
||||
icon: 'image',
|
||||
text: 'AttachPhoto',
|
||||
onClick: () => onAttachMediaClick(true, false),
|
||||
verify: () => getSendMediaRights().then(({photos, videos}) => photos && !videos)
|
||||
}, {
|
||||
icon: 'image',
|
||||
text: 'AttachVideo',
|
||||
onClick: () => onAttachMediaClick(false, true),
|
||||
verify: () => getSendMediaRights().then(({photos, videos}) => !photos && videos)
|
||||
}, */ {
|
||||
icon: 'document',
|
||||
text: 'Chat.Input.Attach.Document',
|
||||
onClick: () => {
|
||||
|
@ -604,17 +635,26 @@ export default class ChatInput {
|
|||
this.fileInput.removeAttribute('accept');
|
||||
this.willAttachType = 'document';
|
||||
this.fileInput.click();
|
||||
},
|
||||
verify: () => this.chat.canSend('send_media')
|
||||
}
|
||||
// verify: () => this.chat.canSend('send_docs')
|
||||
}, {
|
||||
icon: 'poll',
|
||||
text: 'Poll',
|
||||
onClick: () => {
|
||||
onClick: async() => {
|
||||
const action: ChatRights = 'send_polls';
|
||||
if(!(await this.chat.canSend(action))) {
|
||||
toastNew({langPackKey: POSTING_NOT_ALLOWED_MAP[action]});
|
||||
return;
|
||||
}
|
||||
|
||||
PopupElement.createPopup(PopupCreatePoll, this.chat).show();
|
||||
},
|
||||
verify: () => (this.chat.peerId.isAnyChat() || this.chat.isBot) && this.chat.canSend('send_polls')
|
||||
verify: () => this.chat.peerId.isAnyChat() || this.chat.isBot
|
||||
}];
|
||||
|
||||
// preload the bots
|
||||
this.managers.appAttachMenuBotsManager.getAttachMenuBots();
|
||||
|
||||
const attachMenuButtons = this.attachMenuButtons.slice();
|
||||
this.attachMenu = ButtonMenuToggle({
|
||||
buttonOptions: {noRipple: true},
|
||||
|
@ -1368,6 +1408,7 @@ export default class ChatInput {
|
|||
canPinMessage,
|
||||
isBot,
|
||||
canSend,
|
||||
canSendPlain,
|
||||
neededFakeContainer,
|
||||
ackedPeerFull,
|
||||
ackedScheduledMids,
|
||||
|
@ -1377,7 +1418,8 @@ export default class ChatInput {
|
|||
this.managers.appPeersManager.isBroadcast(peerId),
|
||||
this.managers.appPeersManager.canPinMessage(peerId),
|
||||
this.managers.appPeersManager.isBot(peerId),
|
||||
this.chat.canSend(),
|
||||
this.chat.canSend('send_messages'),
|
||||
this.chat.canSend('send_plain'),
|
||||
this.getNeededFakeContainer(startParam),
|
||||
modifyAckedPromise(this.managers.acknowledged.appProfileManager.getProfileByPeerId(peerId)),
|
||||
btnScheduled ? modifyAckedPromise(this.managers.acknowledged.appMessagesManager.getScheduledMessages(peerId)) : undefined,
|
||||
|
@ -1385,7 +1427,7 @@ export default class ChatInput {
|
|||
this.filterAttachMenuButtons()
|
||||
]);
|
||||
|
||||
const placeholderKey = this.messageInput ? await this.getPlaceholderKey() : undefined;
|
||||
const placeholderKey = this.messageInput ? await this.getPlaceholderKey(canSendPlain) : undefined;
|
||||
|
||||
return () => {
|
||||
// console.warn('[input] finishpeerchange start');
|
||||
|
@ -1394,7 +1436,6 @@ export default class ChatInput {
|
|||
goDownBtn.classList.toggle('is-broadcast', isBroadcast);
|
||||
goDownBtn.classList.remove('hide');
|
||||
|
||||
this.messageInputField?.onFakeInput();
|
||||
|
||||
if(this.goDownUnreadBadge) {
|
||||
this.setUnreadCount();
|
||||
|
@ -1439,27 +1480,18 @@ export default class ChatInput {
|
|||
}
|
||||
}
|
||||
|
||||
if(previousSendAs) {
|
||||
previousSendAs.destroy();
|
||||
}
|
||||
|
||||
if(setSendAsCallback) {
|
||||
setSendAsCallback();
|
||||
}
|
||||
|
||||
if(replyKeyboard) {
|
||||
replyKeyboard.setPeer(peerId);
|
||||
}
|
||||
|
||||
if(sendMenu) {
|
||||
sendMenu.setPeerId(peerId);
|
||||
}
|
||||
previousSendAs?.destroy();
|
||||
setSendAsCallback?.();
|
||||
replyKeyboard?.setPeer(peerId);
|
||||
sendMenu?.setPeerId(peerId);
|
||||
|
||||
if(this.messageInput) {
|
||||
this.updateMessageInput(canSend, placeholderKey, filteredAttachMenuButtons);
|
||||
this.updateMessageInput(canSend, canSendPlain, placeholderKey, filteredAttachMenuButtons);
|
||||
this.messageInput.dataset.peerId = '' + peerId;
|
||||
}
|
||||
|
||||
this.messageInputField?.onFakeInput(undefined, true);
|
||||
|
||||
let haveSomethingInControl = false;
|
||||
if(this.pinnedControlBtn) {
|
||||
const good = this.chat.type === 'pinned';
|
||||
|
@ -1530,10 +1562,13 @@ export default class ChatInput {
|
|||
this.updateOffset('commands', forwards, skipAnimation, useRafs);
|
||||
}
|
||||
|
||||
private async getPlaceholderKey() {
|
||||
private async getPlaceholderKey(canSend?: boolean) {
|
||||
canSend ??= await this.chat.canSend('send_plain');
|
||||
const {peerId, threadId, isForum} = this.chat;
|
||||
let key: LangPackKey;
|
||||
if(threadId && !isForum) {
|
||||
if(!canSend) {
|
||||
key = 'Channel.Persmission.MessageBlock';
|
||||
} else if(threadId && !isForum) {
|
||||
key = 'Comment';
|
||||
} else if(await this.managers.appPeersManager.isBroadcast(peerId)) {
|
||||
key = 'ChannelBroadcast';
|
||||
|
@ -1562,13 +1597,17 @@ export default class ChatInput {
|
|||
private filterAttachMenuButtons() {
|
||||
if(!this.attachMenuButtons) return;
|
||||
return filterAsync(this.attachMenuButtons, (button) => {
|
||||
return button.verify();
|
||||
return button.verify ? button.verify() : true;
|
||||
});
|
||||
}
|
||||
|
||||
public updateMessageInput(canSend: boolean, placeholderKey: LangPackKey, visible: ChatInput['attachMenuButtons']) {
|
||||
public updateMessageInput(
|
||||
canSend: boolean,
|
||||
canSendPlain: boolean,
|
||||
placeholderKey: LangPackKey,
|
||||
visible: ChatInput['attachMenuButtons']
|
||||
) {
|
||||
const {chatInput, attachMenu, messageInput} = this;
|
||||
const {peerId, threadId} = this.chat;
|
||||
const isHidden = chatInput.classList.contains('is-hidden');
|
||||
const willBeHidden = !canSend;
|
||||
if(isHidden !== willBeHidden) {
|
||||
|
@ -1580,14 +1619,18 @@ export default class ChatInput {
|
|||
|
||||
this.updateMessageInputPlaceholder(placeholderKey);
|
||||
|
||||
if(!canSend) {
|
||||
messageInput.contentEditable = 'inherit';
|
||||
if(!canSend || !canSendPlain) {
|
||||
messageInput.contentEditable = 'false';
|
||||
|
||||
if(!canSendPlain) {
|
||||
this.messageInputField.onFakeInput(undefined, true);
|
||||
}
|
||||
} else {
|
||||
messageInput.contentEditable = 'true';
|
||||
this.setDraft(undefined, false);
|
||||
|
||||
if(!messageInput.innerHTML) {
|
||||
this.messageInputField.onFakeInput();
|
||||
this.messageInputField.onFakeInput(undefined, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1651,6 +1694,14 @@ export default class ChatInput {
|
|||
}
|
||||
});
|
||||
|
||||
attachClickEvent(this.messageInput, (e) => {
|
||||
if(!this.canSendPlain()) {
|
||||
toastNew({
|
||||
langPackKey: POSTING_NOT_ALLOWED_MAP['send_plain']
|
||||
});
|
||||
}
|
||||
}, {listenerSetter: this.listenerSetter});
|
||||
|
||||
if(IS_TOUCH_SUPPORTED) {
|
||||
attachClickEvent(this.messageInput, (e) => {
|
||||
if(emoticonsDropdown.isActive()) {
|
||||
|
@ -1706,6 +1757,10 @@ export default class ChatInput {
|
|||
});
|
||||
}
|
||||
|
||||
public canSendPlain() {
|
||||
return !(!this.messageInput.isContentEditable && !this.chatInput.classList.contains('is-hidden'));
|
||||
}
|
||||
|
||||
private prepareDocumentExecute = () => {
|
||||
this.executedHistory.push(this.messageInput.innerHTML);
|
||||
return () => this.canUndoFromHTML = this.messageInput.innerHTML;
|
||||
|
@ -1877,9 +1932,7 @@ export default class ChatInput {
|
|||
|
||||
// checkForSingle();
|
||||
// saveExecuted();
|
||||
if(this.appImManager.markupTooltip) {
|
||||
this.appImManager.markupTooltip.setActiveMarkupButton();
|
||||
}
|
||||
this.appImManager.markupTooltip?.setActiveMarkupButton();
|
||||
|
||||
if(textNode) {
|
||||
(textNode.parentElement === this.messageInput ? textNode : textNode.parentElement).remove();
|
||||
|
@ -1957,7 +2010,7 @@ export default class ChatInput {
|
|||
}
|
||||
};
|
||||
|
||||
private onMessageInput = (e?: Event) => {
|
||||
public onMessageInput = (e?: Event) => {
|
||||
// * validate due to manual formatting through browser's context menu
|
||||
/* const inputType = (e as InputEvent).inputType;
|
||||
console.log('message input event', e);
|
||||
|
@ -1999,17 +2052,15 @@ export default class ChatInput {
|
|||
}
|
||||
}
|
||||
|
||||
// console.log('messageInput url:', url);
|
||||
|
||||
if(this.lastUrl !== url) {
|
||||
this.lastUrl = url;
|
||||
// this.willSendWebPage = null;
|
||||
const promise = this.getWebPagePromise = this.managers.appWebPagesManager.getWebPage(url).then((webpage) => {
|
||||
const promise = this.getWebPagePromise = Promise.all([
|
||||
this.managers.appWebPagesManager.getWebPage(url),
|
||||
this.chat.canSend('embed_links')
|
||||
]).then(([webpage, canEmbedLinks]) => {
|
||||
if(this.getWebPagePromise === promise) this.getWebPagePromise = undefined;
|
||||
if(this.lastUrl !== url) return;
|
||||
if(webpage._ === 'webPage') {
|
||||
// console.log('got webpage: ', webpage);
|
||||
|
||||
if(webpage._ === 'webPage' && canEmbedLinks) {
|
||||
this.setTopInfo('webpage', () => {}, webpage.site_name || webpage.title || 'Webpage', webpage.description || webpage.url || '');
|
||||
delete this.noWebPage;
|
||||
this.willSendWebPage = webpage;
|
||||
|
@ -2039,31 +2090,27 @@ export default class ChatInput {
|
|||
this.managers.appMessagesManager.setTyping(this.chat.peerId, {_: 'sendMessageCancelAction'}, undefined, this.chat.threadId);
|
||||
}
|
||||
|
||||
if(this.appImManager.markupTooltip) {
|
||||
this.appImManager.markupTooltip.hide();
|
||||
}
|
||||
this.appImManager.markupTooltip?.hide();
|
||||
|
||||
// * Chrome has a bug - it will preserve the formatting if the input with monospace text is cleared
|
||||
// * so have to reset formatting
|
||||
if(document.activeElement === this.messageInput) {
|
||||
// document.execCommand('styleWithCSS', false, 'true');
|
||||
if(document.activeElement === this.messageInput && !IS_MOBILE) {
|
||||
setTimeout(() => {
|
||||
if(document.activeElement === this.messageInput) {
|
||||
this.resetCurrentFontFormatting();
|
||||
this.messageInput.textContent = '1';
|
||||
placeCaretAtEnd(this.messageInput);
|
||||
this.messageInput.textContent = '';
|
||||
}
|
||||
}, 0);
|
||||
// document.execCommand('styleWithCSS', false, 'false');
|
||||
}
|
||||
} else {
|
||||
const time = Date.now();
|
||||
if((time - this.lastTimeType) >= 6000) {
|
||||
if((time - this.lastTimeType) >= 6000 && e?.isTrusted) {
|
||||
this.lastTimeType = time;
|
||||
this.managers.appMessagesManager.setTyping(this.chat.peerId, {_: 'sendMessageTypingAction'}, undefined, this.chat.threadId);
|
||||
}
|
||||
|
||||
if(this.botCommands) {
|
||||
this.botCommands.toggle(true);
|
||||
}
|
||||
this.botCommands?.toggle(true);
|
||||
}
|
||||
|
||||
if(this.botCommands) {
|
||||
|
@ -2080,6 +2127,13 @@ export default class ChatInput {
|
|||
};
|
||||
|
||||
public insertAtCaret(insertText: string, insertEntity?: MessageEntity, isHelper = true) {
|
||||
if(!this.canSendPlain()) {
|
||||
toastNew({
|
||||
langPackKey: POSTING_NOT_ALLOWED_MAP['send_plain']
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
RichInputHandler.getInstance().makeFocused(this.messageInput);
|
||||
|
||||
const {value: fullValue, caretPos, entities} = getRichValueWithCaret(this.messageInput);
|
||||
|
@ -2192,7 +2246,12 @@ export default class ChatInput {
|
|||
}
|
||||
|
||||
public onEmojiSelected = (emoji: ReturnType<typeof getEmojiFromElement>, autocomplete: boolean) => {
|
||||
const entity: MessageEntity = emoji.docId ? {_: 'messageEntityCustomEmoji', document_id: emoji.docId, length: emoji.emoji.length, offset: 0} : getEmojiEntityFromEmoji(emoji.emoji);
|
||||
const entity: MessageEntity = emoji.docId ? {
|
||||
_: 'messageEntityCustomEmoji',
|
||||
document_id: emoji.docId,
|
||||
length: emoji.emoji.length,
|
||||
offset: 0
|
||||
} : getEmojiEntityFromEmoji(emoji.emoji);
|
||||
this.insertAtCaret(emoji.emoji, entity, autocomplete);
|
||||
};
|
||||
|
||||
|
@ -2255,12 +2314,17 @@ export default class ChatInput {
|
|||
}
|
||||
}
|
||||
|
||||
foundHelper = this.checkInlineAutocomplete(value, foundHelper);
|
||||
let canSendInline: boolean;
|
||||
if(!foundHelper) {
|
||||
canSendInline = await this.chat.canSend('send_inline');
|
||||
}
|
||||
|
||||
foundHelper = this.checkInlineAutocomplete(value, canSendInline, foundHelper);
|
||||
|
||||
this.autocompleteHelperController.hideOtherHelpers(foundHelper);
|
||||
}
|
||||
|
||||
private checkInlineAutocomplete(value: string, foundHelper?: AutocompleteHelper): AutocompleteHelper {
|
||||
private checkInlineAutocomplete(value: string, canSendInline: boolean, foundHelper?: AutocompleteHelper): AutocompleteHelper {
|
||||
let needPlaceholder = false;
|
||||
|
||||
const setPreloaderShow = (show: boolean) => {
|
||||
|
@ -2268,6 +2332,10 @@ export default class ChatInput {
|
|||
return;
|
||||
}
|
||||
|
||||
if(show && !canSendInline) {
|
||||
show = false;
|
||||
}
|
||||
|
||||
SetTransition({
|
||||
element: this.btnPreloader,
|
||||
className: 'show',
|
||||
|
@ -2293,7 +2361,7 @@ export default class ChatInput {
|
|||
setPreloaderShow(true);
|
||||
}
|
||||
|
||||
this.inlineHelper.checkQuery(this.chat.peerId, username, query).then(({user, renderPromise}) => {
|
||||
this.inlineHelper.checkQuery(this.chat.peerId, username, query, canSendInline).then(({user, renderPromise}) => {
|
||||
if(needPlaceholder && user.bot_inline_placeholder) {
|
||||
this.messageInput.dataset.inlinePlaceholder = user.bot_inline_placeholder;
|
||||
}
|
||||
|
@ -2348,8 +2416,9 @@ export default class ChatInput {
|
|||
}
|
||||
} else {
|
||||
const isAnyChat = this.chat.peerId.isAnyChat();
|
||||
if(isAnyChat && !(await this.chat.canSend('send_media'))) {
|
||||
toast(POSTING_MEDIA_NOT_ALLOWED);
|
||||
const flag: ChatRights = 'send_voices';
|
||||
if(isAnyChat && !(await this.chat.canSend(flag))) {
|
||||
toastNew({langPackKey: POSTING_NOT_ALLOWED_MAP[flag]});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2477,7 +2546,7 @@ export default class ChatInput {
|
|||
}
|
||||
};
|
||||
|
||||
private onHelperCancel = async(e?: Event, force?: boolean) => {
|
||||
public onHelperCancel = async(e?: Event, force?: boolean) => {
|
||||
if(e) {
|
||||
cancelEvent(e);
|
||||
}
|
||||
|
@ -2770,7 +2839,7 @@ export default class ChatInput {
|
|||
|
||||
const flag = document.type === 'sticker' ? 'send_stickers' : (document.type === 'gif' ? 'send_gifs' : 'send_media');
|
||||
if(this.chat.peerId.isAnyChat() && !(await this.chat.canSend(flag))) {
|
||||
toast(POSTING_MEDIA_NOT_ALLOWED);
|
||||
toastNew({langPackKey: POSTING_NOT_ALLOWED_MAP[flag]});
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -3111,31 +3180,4 @@ export default class ChatInput {
|
|||
|
||||
return container;
|
||||
}
|
||||
|
||||
// public saveScroll() {
|
||||
// this.scrollTop = this.chat.bubbles.scrollable.container.scrollTop;
|
||||
// this.scrollOffsetTop = this.chatInput.offsetTop;
|
||||
// }
|
||||
|
||||
// public restoreScroll() {
|
||||
// if(this.chatInput.style.display) return;
|
||||
// //console.log('input resize', offsetTop, this.chatInput.offsetTop);
|
||||
// let newOffsetTop = this.chatInput.offsetTop;
|
||||
// let container = this.chat.bubbles.scrollable.container;
|
||||
// let scrollTop = container.scrollTop;
|
||||
// let clientHeight = container.clientHeight;
|
||||
// let maxScrollTop = container.scrollHeight;
|
||||
|
||||
// if(newOffsetTop < this.scrollOffsetTop) {
|
||||
// this.scrollDiff = this.scrollOffsetTop - newOffsetTop;
|
||||
// container.scrollTop += this.scrollDiff;
|
||||
// } else if(scrollTop !== this.scrollTop) {
|
||||
// let endDiff = maxScrollTop - (scrollTop + clientHeight);
|
||||
// if(endDiff < this.scrollDiff/* && false */) {
|
||||
// //container.scrollTop -= endDiff;
|
||||
// } else {
|
||||
// container.scrollTop -= this.scrollDiff;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import replaceContent from '../../helpers/dom/replaceContent';
|
||||
import {Middleware} from '../../helpers/middleware';
|
||||
import limitSymbols from '../../helpers/string/limitSymbols';
|
||||
import {Document, Message, MessageMedia, Photo, WebPage} from '../../layer';
|
||||
import {Document, Message, MessageMedia, Photo, WebPage, VideoSize} from '../../layer';
|
||||
import appImManager from '../../lib/appManagers/appImManager';
|
||||
import choosePhotoSize from '../../lib/appManagers/utils/photos/choosePhotoSize';
|
||||
import wrapEmojiText from '../../lib/richTextProcessor/wrapEmojiText';
|
||||
|
@ -85,7 +85,7 @@ export async function wrapReplyDivAndCaption(options: {
|
|||
middleware,
|
||||
loadPromises,
|
||||
withoutPreloader: true,
|
||||
videoSize: document.video_thumbs[0],
|
||||
videoSize: document.video_thumbs[0] as Extract<VideoSize, VideoSize.videoSize>,
|
||||
group: animationGroup
|
||||
});
|
||||
} else {
|
||||
|
|
|
@ -37,6 +37,7 @@ import {AppManagers} from '../../lib/appManagers/managers';
|
|||
import {attachContextMenuListener} from '../../helpers/dom/attachContextMenuListener';
|
||||
import filterUnique from '../../helpers/array/filterUnique';
|
||||
import appImManager from '../../lib/appManagers/appImManager';
|
||||
import {Message} from '../../layer';
|
||||
|
||||
const accumulateMapSet = (map: Map<any, Set<number>>) => {
|
||||
return [...map.values()].reduce((acc, v) => acc + v.size, 0);
|
||||
|
@ -360,7 +361,7 @@ class AppSelection extends EventListenerBase<{
|
|||
cantDelete = !size;
|
||||
const cantSend = !size;
|
||||
for(const [peerId, mids] of this.selectedMids) {
|
||||
const storageKey: MessagesStorageKey = `${peerId}_${this.isScheduled ? 'scheduled' : 'history'}`;
|
||||
const storageKey = this.getStorageKey(peerId);
|
||||
const r = await this.managers.appMessagesManager.cantForwardDeleteMids(storageKey, Array.from(mids));
|
||||
cantForward = r.cantForward;
|
||||
cantDelete = r.cantDelete;
|
||||
|
@ -371,6 +372,20 @@ class AppSelection extends EventListenerBase<{
|
|||
this.onUpdateContainer?.(cantForward, cantDelete, cantSend);
|
||||
}
|
||||
|
||||
private getStorageKey(peerId: PeerId): MessagesStorageKey {
|
||||
return `${peerId}_${this.isScheduled ? 'scheduled' : 'history'}`;
|
||||
}
|
||||
|
||||
public getSelectedMessages() {
|
||||
const selectedMessagesPromises: Promise<Message.message | Message.messageService>[] = [];
|
||||
this.selectedMids.forEach((mids, peerId) => {
|
||||
const storageKey = this.getStorageKey(peerId);
|
||||
const p = Array.from(mids).map((mid) => this.managers.appMessagesManager.getMessageFromStorage(storageKey, mid));
|
||||
selectedMessagesPromises.push(...p);
|
||||
});
|
||||
return Promise.all(selectedMessagesPromises);
|
||||
}
|
||||
|
||||
public toggleSelection(toggleCheckboxes = true, forceSelection = false) {
|
||||
const wasSelecting = this.isSelecting;
|
||||
const size = this.selectedMids.size;
|
||||
|
@ -988,9 +1003,10 @@ export default class ChatSelection extends AppSelection {
|
|||
void this.selectionInputWrapper.offsetLeft; // reflow
|
||||
// background.style.opacity = '';
|
||||
this.selectionInputWrapper.style.opacity = '';
|
||||
left.style.transform = '';
|
||||
right.style.transform = '';
|
||||
}
|
||||
|
||||
this.selectionLeft.style.transform = '';
|
||||
this.selectionRight.style.transform = '';
|
||||
} else if(this.selectionLeft && translateButtonsX !== undefined) {
|
||||
this.selectionLeft.style.transform = `translateX(-${translateButtonsX}px)`;
|
||||
this.selectionRight.style.transform = `translateX(${translateButtonsX}px)`;
|
||||
|
|
|
@ -448,7 +448,7 @@ export default class ChatTopbar {
|
|||
});
|
||||
},
|
||||
placeholder: 'ShareModal.Search.Placeholder',
|
||||
chatRightsAction: 'send_messages',
|
||||
chatRightsActions: ['send_plain'],
|
||||
selfPresence: 'ChatYourSelf'
|
||||
});
|
||||
},
|
||||
|
|
|
@ -36,7 +36,7 @@ export default class CheckboxField {
|
|||
const label = this.label = document.createElement('label');
|
||||
label.classList.add('checkbox-field');
|
||||
|
||||
if(options.restriction) {
|
||||
if(options.restriction && !options.toggle) {
|
||||
label.classList.add('checkbox-field-restriction');
|
||||
}
|
||||
|
||||
|
@ -109,6 +109,10 @@ export default class CheckboxField {
|
|||
if(options.toggle) {
|
||||
label.classList.add('checkbox-field-toggle');
|
||||
|
||||
if(options.restriction) {
|
||||
label.classList.add('checkbox-field-toggle-restriction');
|
||||
}
|
||||
|
||||
const toggle = document.createElement('div');
|
||||
toggle.classList.add('checkbox-toggle');
|
||||
label.append(toggle);
|
||||
|
|
|
@ -37,6 +37,9 @@ import BezierEasing from '../../vendor/bezier-easing';
|
|||
import RichInputHandler from '../../helpers/dom/richInputHandler';
|
||||
import {getCaretPosF} from '../../helpers/dom/getCaretPosNew';
|
||||
import ListenerSetter from '../../helpers/listenerSetter';
|
||||
import {ChatRights} from '../../lib/appManagers/appChatsManager';
|
||||
import {toastNew} from '../toast';
|
||||
import {POSTING_NOT_ALLOWED_MAP} from '../chat/input';
|
||||
|
||||
export const EMOTICONSSTICKERGROUP: AnimationItemGroup = 'emoticons-dropdown';
|
||||
|
||||
|
@ -79,12 +82,19 @@ export class EmoticonsDropdown extends DropdownHover {
|
|||
private savedRange: Range;
|
||||
private managers: AppManagers;
|
||||
|
||||
private rights: {[action in ChatRights]?: boolean};
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
element: document.getElementById('emoji-dropdown') as HTMLDivElement,
|
||||
ignoreOutClickClassName: 'input-message-input'
|
||||
});
|
||||
|
||||
this.rights = {
|
||||
send_gifs: undefined,
|
||||
send_stickers: undefined
|
||||
};
|
||||
|
||||
this.addEventListener('open', async() => {
|
||||
if(IS_TOUCH_SUPPORTED) {
|
||||
// appImManager.chat.input.saveScroll();
|
||||
|
@ -322,6 +332,17 @@ export class EmoticonsDropdown extends DropdownHover {
|
|||
return;
|
||||
}
|
||||
|
||||
const rights: {[tabId: number]: ChatRights} = {
|
||||
[this.stickersTab.tabId]: 'send_stickers',
|
||||
[this.gifsTab.tabId]: 'send_gifs'
|
||||
};
|
||||
|
||||
const action = rights[id];
|
||||
if(action && !this.rights[action]) {
|
||||
toastNew({langPackKey: POSTING_NOT_ALLOWED_MAP[action]});
|
||||
return false;
|
||||
}
|
||||
|
||||
animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP);
|
||||
|
||||
this.tabId = id;
|
||||
|
@ -331,19 +352,19 @@ export class EmoticonsDropdown extends DropdownHover {
|
|||
|
||||
private checkRights = async() => {
|
||||
const {peerId, threadId} = appImManager.chat;
|
||||
const children = this.tabsEl.children;
|
||||
const tabsElements = Array.from(children) as HTMLElement[];
|
||||
|
||||
const [canSendStickers, canSendGifs] = await Promise.all([
|
||||
this.managers.appMessagesManager.canSendToPeer(peerId, threadId, 'send_stickers'),
|
||||
this.managers.appMessagesManager.canSendToPeer(peerId, threadId, 'send_gifs')
|
||||
]);
|
||||
const actions = Object.keys(this.rights) as ChatRights[];
|
||||
|
||||
tabsElements[this.stickersTab.tabId + 1].toggleAttribute('disabled', !canSendStickers);
|
||||
tabsElements[this.gifsTab.tabId + 1].toggleAttribute('disabled', !canSendGifs);
|
||||
const rights = await Promise.all(actions.map((action) => {
|
||||
return this.managers.appMessagesManager.canSendToPeer(peerId, threadId, action);
|
||||
}));
|
||||
|
||||
actions.forEach((action, idx) => {
|
||||
this.rights[action] = rights[idx];
|
||||
});
|
||||
|
||||
const active = this.tabsEl.querySelector('.active');
|
||||
if(active && whichChild(active) !== (this.emojiTab.tabId + 1) && (!canSendStickers || !canSendGifs)) {
|
||||
if(active && whichChild(active) !== (this.emojiTab.tabId + 1) && (!this.rights['send_stickers'] || !this.rights['send_gifs'])) {
|
||||
this.selectTab(this.emojiTab.tabId, false);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -177,7 +177,7 @@ let init = () => {
|
|||
|
||||
const peerId = (input.dataset.peerId || NULL_PEER_ID).toPeerId();
|
||||
if(html.trim()) {
|
||||
console.log(html.replace(/ (style|class|id)=".+?"/g, ''));
|
||||
// console.log(html.replace(/ (style|class|id)=".+?"/g, ''));
|
||||
|
||||
html = html.replace(/<style([\s\S]*)<\/style>/, '');
|
||||
html = html.replace(/<!--([\s\S]*)-->/, '');
|
||||
|
@ -260,7 +260,7 @@ let init = () => {
|
|||
mergeEntities(entities, entities2);
|
||||
}
|
||||
|
||||
console.log('usePlainText', usePlainText);
|
||||
// console.log('usePlainText', usePlainText);
|
||||
}
|
||||
|
||||
if(usePlainText) {
|
||||
|
|
|
@ -42,20 +42,22 @@ export default class InputFieldAnimated extends InputField {
|
|||
this.inputFake.className = this.input.className + ' input-field-input-fake';
|
||||
}
|
||||
|
||||
public onFakeInput(setHeight = true) {
|
||||
public onFakeInput(setHeight = true, noAnimation?: boolean) {
|
||||
const {scrollHeight: newHeight/* , clientHeight */} = this.inputFake;
|
||||
/* if(this.wasInputFakeClientHeight && this.wasInputFakeClientHeight !== clientHeight) {
|
||||
this.input.classList.add('no-scrollbar'); // ! в сафари может вообще не появиться скролл после анимации, так как ему нужен полный reflow блока с overflow.
|
||||
this.showScrollDebounced();
|
||||
} */
|
||||
|
||||
noAnimation ??= !this.input.isContentEditable;
|
||||
|
||||
const currentHeight = +this.input.style.height.replace('px', '');
|
||||
if(currentHeight === newHeight) {
|
||||
return;
|
||||
}
|
||||
|
||||
const TRANSITION_DURATION_FACTOR = 50;
|
||||
const transitionDuration = Math.round(
|
||||
const transitionDuration = noAnimation ? 0 : Math.round(
|
||||
TRANSITION_DURATION_FACTOR * Math.log(Math.abs(newHeight - currentHeight))
|
||||
);
|
||||
|
||||
|
|
|
@ -4,15 +4,19 @@
|
|||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
import type {ChatRights} from '../../lib/appManagers/appChatsManager';
|
||||
import flatten from '../../helpers/array/flatten';
|
||||
import appImManager from '../../lib/appManagers/appImManager';
|
||||
import rootScope from '../../lib/rootScope';
|
||||
import {toastNew} from '../toast';
|
||||
import PopupPickUser from './pickUser';
|
||||
import getMediaFromMessage from '../../lib/appManagers/utils/messages/getMediaFromMessage';
|
||||
|
||||
export default class PopupForward extends PopupPickUser {
|
||||
constructor(
|
||||
peerIdMids?: {[fromPeerId: PeerId]: number[]},
|
||||
onSelect?: (peerId: PeerId) => Promise<void> | void
|
||||
onSelect?: (peerId: PeerId) => Promise<void> | void,
|
||||
chatRightsAction: ChatRights[] = ['send_plain']
|
||||
) {
|
||||
super({
|
||||
peerTypes: ['dialogs', 'contacts'],
|
||||
|
@ -43,8 +47,71 @@ export default class PopupForward extends PopupPickUser {
|
|||
appImManager.chat.input.initMessagesForward(peerIdMids);
|
||||
},
|
||||
placeholder: 'ShareModal.Search.ForwardPlaceholder',
|
||||
chatRightsAction: 'send_messages',
|
||||
chatRightsActions: chatRightsAction,
|
||||
selfPresence: 'ChatYourSelf'
|
||||
});
|
||||
}
|
||||
|
||||
public static async create(...args: ConstructorParameters<typeof PopupForward>) {
|
||||
const [peerIdMids] = args;
|
||||
const messagesPromises = Object.keys(peerIdMids).map((peerId) => {
|
||||
const mids = peerIdMids[peerId as any as number];
|
||||
return mids.map((mid) => {
|
||||
return rootScope.managers.appMessagesManager.getMessageByPeer(peerId.toPeerId(), mid);
|
||||
});
|
||||
});
|
||||
|
||||
const messages = await Promise.all(flatten(messagesPromises));
|
||||
const actions: Set<ChatRights> = new Set();
|
||||
messages.forEach((message) => {
|
||||
if(!message) {
|
||||
return;
|
||||
}
|
||||
|
||||
const media = getMediaFromMessage(message);
|
||||
let action: ChatRights;
|
||||
if(!media) {
|
||||
if(message.viaBotId) {
|
||||
action = 'send_inline';
|
||||
} else {
|
||||
action = 'send_plain';
|
||||
}
|
||||
} else {
|
||||
if(media._ === 'webPage') {
|
||||
action = 'embed_links';
|
||||
} else if(media._ === 'photo') {
|
||||
action = 'send_photos';
|
||||
} else if(media._ === 'game') {
|
||||
action = 'send_games';
|
||||
} else {
|
||||
switch(media.type) {
|
||||
case 'audio':
|
||||
action = 'send_audios';
|
||||
break;
|
||||
case 'gif':
|
||||
action = 'send_gifs';
|
||||
break;
|
||||
case 'round':
|
||||
action = 'send_roundvideos';
|
||||
break;
|
||||
case 'sticker':
|
||||
action = 'send_stickers';
|
||||
break;
|
||||
case 'voice':
|
||||
action = 'send_voices';
|
||||
break;
|
||||
default:
|
||||
action = 'send_docs';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(action) {
|
||||
actions.add(action);
|
||||
}
|
||||
});
|
||||
|
||||
new PopupForward(args[0], args[1], Array.from(actions));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
|
||||
import type Chat from '../chat/chat';
|
||||
import type {SendFileDetails} from '../../lib/appManagers/appMessagesManager';
|
||||
import type {ChatRights} from '../../lib/appManagers/appChatsManager';
|
||||
import PopupElement from '.';
|
||||
import Scrollable from '../scrollable';
|
||||
import {toast} from '../toast';
|
||||
import {toast, toastNew} from '../toast';
|
||||
import SendContextMenu from '../chat/sendContextMenu';
|
||||
import {createPosterFromMedia, createPosterFromVideo} from '../../helpers/createPoster';
|
||||
import {MyDocument} from '../../lib/appManagers/appDocsManager';
|
||||
|
@ -41,6 +42,11 @@ import {renderImageFromUrlPromise} from '../../helpers/dom/renderImageFromUrl';
|
|||
import ButtonMenuToggle from '../buttonMenuToggle';
|
||||
import partition from '../../helpers/array/partition';
|
||||
import InputFieldAnimated from '../inputFieldAnimated';
|
||||
import IMAGE_MIME_TYPES_SUPPORTED from '../../environment/imageMimeTypesSupport';
|
||||
import VIDEO_MIME_TYPES_SUPPORTED from '../../environment/videoMimeTypesSupport';
|
||||
import rootScope from '../../lib/rootScope';
|
||||
import shake from '../../helpers/dom/shake';
|
||||
import AUDIO_MIME_TYPES_SUPPORTED from '../../environment/audioMimeTypeSupport';
|
||||
|
||||
type SendFileParams = SendFileDetails & {
|
||||
file?: File,
|
||||
|
@ -96,6 +102,29 @@ export default class PopupNewMedia extends PopupElement {
|
|||
this.construct(willAttachType);
|
||||
}
|
||||
|
||||
public static async canSend(peerId: PeerId, onlyVisible?: boolean) {
|
||||
const actions: ChatRights[] = [
|
||||
'send_photos',
|
||||
'send_videos',
|
||||
'send_docs',
|
||||
'send_audios',
|
||||
'send_gifs'
|
||||
];
|
||||
|
||||
const actionsPromises = actions.map((action) => {
|
||||
return peerId.isAnyChat() && !onlyVisible ? rootScope.managers.appChatsManager.hasRights(peerId.toChatId(), action) : true;
|
||||
});
|
||||
|
||||
const out: {[action in ChatRights]?: boolean} = {};
|
||||
|
||||
const results = await Promise.all(actionsPromises);
|
||||
actions.forEach((action, idx) => {
|
||||
out[action] = results[idx];
|
||||
})
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
private async construct(willAttachType: PopupNewMedia['willAttach']['type']) {
|
||||
this.willAttach = {
|
||||
type: willAttachType,
|
||||
|
@ -106,6 +135,12 @@ export default class PopupNewMedia extends PopupElement {
|
|||
const captionMaxLength = await this.managers.apiManager.getLimit('caption');
|
||||
this.captionLengthMax = captionMaxLength;
|
||||
|
||||
const canSend = await PopupNewMedia.canSend(this.chat.peerId, true);
|
||||
|
||||
const canSendPhotos = canSend.send_photos;
|
||||
const canSendVideos = canSend.send_videos;
|
||||
const canSendDocs = canSend.send_docs;
|
||||
|
||||
attachClickEvent(this.btnConfirm, () => this.send(), {listenerSetter: this.listenerSetter});
|
||||
|
||||
const btnMenu = await ButtonMenuToggle({
|
||||
|
@ -115,17 +150,35 @@ export default class PopupNewMedia extends PopupElement {
|
|||
icon: 'image',
|
||||
text: 'Popup.Attach.AsMedia',
|
||||
onClick: () => this.changeType('media'),
|
||||
verify: () => this.hasAnyMedia() && this.willAttach.type === 'document'
|
||||
verify: () => {
|
||||
if(!this.hasAnyMedia() || this.willAttach.type !== 'document') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!canSendPhotos && !canSendVideos) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!canSendPhotos || !canSendVideos) {
|
||||
const mimeTypes = canSendPhotos ? IMAGE_MIME_TYPES_SUPPORTED : VIDEO_MIME_TYPES_SUPPORTED;
|
||||
const {media, files} = this.partition(mimeTypes);
|
||||
if(files.length) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}, {
|
||||
icon: 'document',
|
||||
text: 'SendAsFile',
|
||||
onClick: () => this.changeType('document'),
|
||||
verify: () => this.files.length === 1 && this.willAttach.type !== 'document'
|
||||
verify: () => this.files.length === 1 && this.willAttach.type !== 'document' && canSendDocs
|
||||
}, {
|
||||
icon: 'document',
|
||||
text: 'SendAsFiles',
|
||||
onClick: () => this.changeType('document'),
|
||||
verify: () => this.files.length > 1 && this.willAttach.type !== 'document'
|
||||
verify: () => this.files.length > 1 && this.willAttach.type !== 'document' && canSendDocs
|
||||
}, {
|
||||
icon: 'groupmedia',
|
||||
text: 'Popup.Attach.GroupMedia',
|
||||
|
@ -365,8 +418,8 @@ export default class PopupNewMedia extends PopupElement {
|
|||
this.willAttach.type = type;
|
||||
}
|
||||
|
||||
private partition() {
|
||||
const [media, files] = partition(this.willAttach.sendFileDetails, (d) => MEDIA_MIME_TYPES_SUPPORTED.has(d.file.type));
|
||||
private partition(mimeTypes = MEDIA_MIME_TYPES_SUPPORTED) {
|
||||
const [media, files] = partition(this.willAttach.sendFileDetails, (d) => mimeTypes.has(d.file.type));
|
||||
return {
|
||||
media,
|
||||
files
|
||||
|
@ -455,7 +508,69 @@ export default class PopupNewMedia extends PopupElement {
|
|||
}
|
||||
};
|
||||
|
||||
private send(force = false) {
|
||||
private async send(force = false) {
|
||||
let caption = this.messageInputField.value;
|
||||
if(caption.length > this.captionLengthMax) {
|
||||
toast(I18n.format('Error.PreviewSender.CaptionTooLong', true));
|
||||
return;
|
||||
}
|
||||
|
||||
const {peerId, input} = this.chat;
|
||||
|
||||
const canSend = await PopupNewMedia.canSend(peerId);
|
||||
const willAttach = this.willAttach;
|
||||
willAttach.isMedia = willAttach.type === 'media' || undefined;
|
||||
const {sendFileDetails, isMedia} = willAttach;
|
||||
|
||||
let foundBad = false;
|
||||
this.iterate((sendFileParams) => {
|
||||
if(foundBad) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isBad: (LangPackKey | boolean)[] = sendFileParams.map((params) => {
|
||||
const a: [Set<string> | (() => boolean), LangPackKey, ChatRights][] = [
|
||||
[AUDIO_MIME_TYPES_SUPPORTED, 'GlobalAttachAudioRestricted', 'send_audios'],
|
||||
[() => !MEDIA_MIME_TYPES_SUPPORTED.has(params.file.type), 'GlobalAttachDocumentsRestricted', 'send_docs']
|
||||
];
|
||||
|
||||
if(isMedia) {
|
||||
a.unshift(
|
||||
[IMAGE_MIME_TYPES_SUPPORTED, 'GlobalAttachPhotoRestricted', 'send_photos'],
|
||||
[() => VIDEO_MIME_TYPES_SUPPORTED.has(params.file.type as any) && params.noSound, 'GlobalAttachGifRestricted', 'send_gifs'],
|
||||
[VIDEO_MIME_TYPES_SUPPORTED, 'GlobalAttachVideoRestricted', 'send_videos']
|
||||
);
|
||||
}
|
||||
|
||||
const found = a.find(([verify]) => {
|
||||
return typeof(verify) === 'function' ? verify() : verify.has(params.file.type);
|
||||
});
|
||||
|
||||
if(found) {
|
||||
return canSend[found[2]] ? undefined : found[1];
|
||||
}
|
||||
|
||||
return (!isMedia && !canSend.send_docs && 'GlobalAttachDocumentsRestricted') || undefined;
|
||||
});
|
||||
|
||||
const key = isBad.find((i) => typeof(i) === 'string') as LangPackKey;
|
||||
if(key) {
|
||||
toastNew({
|
||||
langPackKey: key
|
||||
});
|
||||
|
||||
if(rootScope.settings.animationsEnabled) {
|
||||
shake(this.body);
|
||||
}
|
||||
}
|
||||
|
||||
foundBad ||= !!key;
|
||||
});
|
||||
|
||||
if(foundBad) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.chat.type === 'scheduled' && !force) {
|
||||
this.chat.input.scheduleSending(() => {
|
||||
this.send(true);
|
||||
|
@ -464,18 +579,6 @@ export default class PopupNewMedia extends PopupElement {
|
|||
return;
|
||||
}
|
||||
|
||||
let caption = this.messageInputField.value;
|
||||
if(caption.length > this.captionLengthMax) {
|
||||
toast(I18n.format('Error.PreviewSender.CaptionTooLong', true));
|
||||
return;
|
||||
}
|
||||
|
||||
const willAttach = this.willAttach;
|
||||
willAttach.isMedia = willAttach.type === 'media' || undefined;
|
||||
const {sendFileDetails, isMedia} = willAttach;
|
||||
|
||||
const {peerId, input} = this.chat;
|
||||
|
||||
const {length} = sendFileDetails;
|
||||
const sendingParams = this.chat.getMessageSendingParams();
|
||||
this.iterate((sendFileParams) => {
|
||||
|
@ -631,7 +734,7 @@ export default class PopupNewMedia extends PopupElement {
|
|||
const file = params.file;
|
||||
|
||||
const isPhoto = file.type.startsWith('image/');
|
||||
const isAudio = file.type.startsWith('audio/');
|
||||
const isAudio = AUDIO_MIME_TYPES_SUPPORTED.has(file.type as any);
|
||||
if(isPhoto || isAudio || file.size < 20e6) {
|
||||
params.objectURL ||= await apiManagerProxy.invoke('createObjectURL', file);
|
||||
}
|
||||
|
@ -860,3 +963,5 @@ export default class PopupNewMedia extends PopupElement {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
(window as any).PopupNewMedia = PopupNewMedia;
|
||||
|
|
|
@ -16,7 +16,7 @@ export default class PopupPickUser extends PopupElement {
|
|||
peerTypes: AppSelectPeers['peerType'],
|
||||
onSelect?: (peerId: PeerId) => Promise<void> | void,
|
||||
placeholder: LangPackKey,
|
||||
chatRightsAction?: AppSelectPeers['chatRightsAction'],
|
||||
chatRightsActions?: AppSelectPeers['chatRightsActions'],
|
||||
peerId?: number,
|
||||
selfPresence?: LangPackKey
|
||||
}) {
|
||||
|
@ -51,7 +51,7 @@ export default class PopupPickUser extends PopupElement {
|
|||
this.selector.input.focus();
|
||||
}
|
||||
},
|
||||
chatRightsAction: options.chatRightsAction,
|
||||
chatRightsActions: options.chatRightsActions,
|
||||
multiSelect: false,
|
||||
rippleEnabled: false,
|
||||
avatarSize: 'abitbigger',
|
||||
|
|
|
@ -149,13 +149,15 @@ export default function ripple(
|
|||
// });
|
||||
};
|
||||
|
||||
const isRippleUnneeded = (e: Event) => e.target !== elem && (
|
||||
['BUTTON', 'A'].includes((e.target as HTMLElement).tagName) ||
|
||||
findUpClassName(e.target as HTMLElement, 'c-ripple') !== r
|
||||
) && (
|
||||
attachListenerTo === elem ||
|
||||
!findUpAsChild(e.target as HTMLElement, attachListenerTo)
|
||||
);
|
||||
const isRippleUnneeded = (e: Event) => {
|
||||
return e.target !== elem && (
|
||||
['BUTTON', 'A'].includes((e.target as HTMLElement).tagName) ||
|
||||
findUpClassName(e.target as HTMLElement, 'c-ripple') !== r
|
||||
) && (
|
||||
attachListenerTo === elem ||
|
||||
!findUpAsChild(e.target as HTMLElement, attachListenerTo)
|
||||
) && !findUpClassName(e.target, 'checkbox-field');
|
||||
};
|
||||
|
||||
// TODO: rename this variable
|
||||
let touchStartFired = false;
|
||||
|
|
|
@ -181,10 +181,15 @@ export default class AppEditChatTab extends SliderSuperTab {
|
|||
|
||||
if(canChangePermissions && !isBroadcast) {
|
||||
const flags = [
|
||||
'send_messages',
|
||||
'send_media',
|
||||
'send_stickers',
|
||||
'send_polls',
|
||||
'send_photos',
|
||||
'send_videos',
|
||||
'send_roundvideos',
|
||||
'send_audios',
|
||||
'send_voices',
|
||||
'send_docs',
|
||||
'send_plain',
|
||||
'embed_links',
|
||||
'invite_users',
|
||||
'pin_messages',
|
||||
|
|
|
@ -4,13 +4,15 @@
|
|||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
import type {ChatRights} from '../../../lib/appManagers/appChatsManager';
|
||||
import flatten from '../../../helpers/array/flatten';
|
||||
import cancelEvent from '../../../helpers/dom/cancelEvent';
|
||||
import {attachClickEvent} from '../../../helpers/dom/clickEvent';
|
||||
import findUpTag from '../../../helpers/dom/findUpTag';
|
||||
import replaceContent from '../../../helpers/dom/replaceContent';
|
||||
import ListenerSetter from '../../../helpers/listenerSetter';
|
||||
import ScrollableLoader from '../../../helpers/scrollableLoader';
|
||||
import {ChannelParticipant, Chat, ChatBannedRights, Update} from '../../../layer';
|
||||
import {ChatRights} from '../../../lib/appManagers/appChatsManager';
|
||||
import {ChannelParticipant, Chat, ChatBannedRights} from '../../../layer';
|
||||
import appDialogsManager, {DialogDom, DIALOG_LIST_ELEMENT_TAG} from '../../../lib/appManagers/appDialogsManager';
|
||||
import {AppManagers} from '../../../lib/appManagers/managers';
|
||||
import combineParticipantBannedRights from '../../../lib/appManagers/utils/chats/combineParticipantBannedRights';
|
||||
|
@ -21,20 +23,32 @@ import I18n, {i18n, join, LangPackKey} from '../../../lib/langPack';
|
|||
import rootScope from '../../../lib/rootScope';
|
||||
import CheckboxField from '../../checkboxField';
|
||||
import PopupPickUser from '../../popups/pickUser';
|
||||
import Row, {CreateRowFromCheckboxField} from '../../row';
|
||||
import Row from '../../row';
|
||||
import SettingSection from '../../settingSection';
|
||||
import {SliderSuperTabEventable} from '../../sliderTab';
|
||||
import {toast} from '../../toast';
|
||||
import AppUserPermissionsTab from './userPermissions';
|
||||
import findUpAsChild from '../../../helpers/dom/findUpAsChild';
|
||||
|
||||
type T = {
|
||||
flags: ChatRights[],
|
||||
text: LangPackKey,
|
||||
exceptionText: LangPackKey,
|
||||
checkboxField?: CheckboxField,
|
||||
nested?: T[],
|
||||
nestedTo?: T,
|
||||
nestedCounter?: HTMLElement,
|
||||
setNestedCounter?: (count: number) => void,
|
||||
toggleWith?: {checked?: ChatRights[], unchecked?: ChatRights[]}
|
||||
};
|
||||
|
||||
export class ChatPermissions {
|
||||
public v: Array<{
|
||||
flags: ChatRights[],
|
||||
text: LangPackKey,
|
||||
exceptionText: LangPackKey,
|
||||
checkboxField?: CheckboxField,
|
||||
}>;
|
||||
private toggleWith: Partial<{[chatRight in ChatRights]: ChatRights[]}>;
|
||||
public v: Array<T>;
|
||||
|
||||
protected chat: Chat.chat | Chat.channel;
|
||||
protected rights: ChatBannedRights.chatBannedRights;
|
||||
protected defaultBannedRights: ChatBannedRights.chatBannedRights;
|
||||
protected restrictionText: LangPackKey;
|
||||
|
||||
constructor(private options: {
|
||||
chatId: ChatId,
|
||||
|
@ -46,79 +60,158 @@ export class ChatPermissions {
|
|||
}
|
||||
|
||||
public async construct() {
|
||||
this.v = [
|
||||
{flags: ['send_messages'], text: 'UserRestrictionsSend', exceptionText: 'UserRestrictionsNoSend'},
|
||||
{flags: ['send_media'], text: 'UserRestrictionsSendMedia', exceptionText: 'UserRestrictionsNoSendMedia'},
|
||||
const mediaNested: T[] = [
|
||||
{flags: ['send_photos'], text: 'UserRestrictionsSendPhotos', exceptionText: 'UserRestrictionsNoSendPhotos'},
|
||||
{flags: ['send_videos'], text: 'UserRestrictionsSendVideos', exceptionText: 'UserRestrictionsNoSendVideos'},
|
||||
{flags: ['send_stickers', 'send_gifs'], text: 'UserRestrictionsSendStickers', exceptionText: 'UserRestrictionsNoSendStickers'},
|
||||
{flags: ['send_polls'], text: 'UserRestrictionsSendPolls', exceptionText: 'UserRestrictionsNoSendPolls'},
|
||||
{flags: ['embed_links'], text: 'UserRestrictionsEmbedLinks', exceptionText: 'UserRestrictionsNoEmbedLinks'},
|
||||
{flags: ['send_audios'], text: 'UserRestrictionsSendMusic', exceptionText: 'UserRestrictionsNoSendMusic'},
|
||||
{flags: ['send_docs'], text: 'UserRestrictionsSendFiles', exceptionText: 'UserRestrictionsNoSendDocs'},
|
||||
{flags: ['send_voices'], text: 'UserRestrictionsSendVoices', exceptionText: 'UserRestrictionsNoSendVoice'},
|
||||
{flags: ['send_roundvideos'], text: 'UserRestrictionsSendRound', exceptionText: 'UserRestrictionsNoSendRound'},
|
||||
{flags: ['embed_links'], text: 'UserRestrictionsEmbedLinks', exceptionText: 'UserRestrictionsNoEmbedLinks', toggleWith: {checked: ['send_plain']}},
|
||||
{flags: ['send_polls'], text: 'UserRestrictionsSendPolls', exceptionText: 'UserRestrictionsNoSendPolls'}
|
||||
];
|
||||
|
||||
const mediaToggleWith = flatten(mediaNested.map(({flags}) => flags));
|
||||
const media: T = {flags: ['send_media'], text: 'UserRestrictionsSendMedia', exceptionText: 'UserRestrictionsNoSendMedia', nested: mediaNested, toggleWith: {unchecked: mediaToggleWith, checked: mediaToggleWith}};
|
||||
|
||||
this.v = [
|
||||
{flags: ['send_plain'], text: 'UserRestrictionsSend', exceptionText: 'UserRestrictionsNoSend', toggleWith: {unchecked: ['embed_links']}},
|
||||
media,
|
||||
{flags: ['invite_users'], text: 'UserRestrictionsInviteUsers', exceptionText: 'UserRestrictionsNoInviteUsers'},
|
||||
{flags: ['pin_messages'], text: 'UserRestrictionsPinMessages', exceptionText: 'UserRestrictionsNoPinMessages'},
|
||||
{flags: ['change_info'], text: 'UserRestrictionsChangeInfo', exceptionText: 'UserRestrictionsNoChangeInfo'}
|
||||
];
|
||||
|
||||
this.toggleWith = {
|
||||
'send_messages': ['send_media', 'send_stickers', 'send_polls', 'embed_links']
|
||||
};
|
||||
mediaNested.forEach((info) => info.nestedTo = media);
|
||||
|
||||
const options = this.options;
|
||||
const chat = await this.managers.appChatsManager.getChat(options.chatId) as Chat.chat | Chat.channel;
|
||||
const defaultBannedRights = chat.default_banned_rights;
|
||||
const rights = options.participant ? combineParticipantBannedRights(chat as Chat.channel, options.participant.banned_rights) : defaultBannedRights;
|
||||
const chat = this.chat = await this.managers.appChatsManager.getChat(options.chatId) as Chat.chat | Chat.channel;
|
||||
const defaultBannedRights = this.defaultBannedRights = chat.default_banned_rights;
|
||||
const rights = this.rights = options.participant ? combineParticipantBannedRights(chat as Chat.channel, options.participant.banned_rights) : defaultBannedRights;
|
||||
|
||||
const restrictionText: LangPackKey = options.participant ? 'UserRestrictionsDisabled' : 'EditCantEditPermissionsPublic';
|
||||
for(const info of this.v) {
|
||||
const mainFlag = info.flags[0];
|
||||
const row = CreateRowFromCheckboxField(
|
||||
info.checkboxField = new CheckboxField({
|
||||
text: info.text,
|
||||
checked: hasRights(chat, mainFlag, rights),
|
||||
restriction: true,
|
||||
listenerSetter: options.listenerSetter
|
||||
})
|
||||
);
|
||||
const {nodes} = this.createRow(info);
|
||||
options.appendTo.append(...nodes);
|
||||
}
|
||||
|
||||
if((
|
||||
options.participant &&
|
||||
defaultBannedRights.pFlags[mainFlag as keyof typeof defaultBannedRights['pFlags']]
|
||||
) || (
|
||||
getPeerActiveUsernames(chat as Chat.channel)[0] &&
|
||||
(
|
||||
info.flags.includes('pin_messages') ||
|
||||
info.flags.includes('change_info')
|
||||
)
|
||||
)
|
||||
) {
|
||||
info.checkboxField.input.disabled = true;
|
||||
this.v.push(...mediaNested);
|
||||
}
|
||||
|
||||
/* options.listenerSetter.add(info.checkboxField.input)('change', (e) => {
|
||||
if(!e.isTrusted) {
|
||||
return;
|
||||
protected createRow(info: T, isNested?: boolean) {
|
||||
const {defaultBannedRights, chat, rights, restrictionText} = this;
|
||||
|
||||
const mainFlag = info.flags[0];
|
||||
const row = new Row({
|
||||
titleLangKey: isNested ? undefined : info.text,
|
||||
checkboxField: info.checkboxField = new CheckboxField({
|
||||
text: isNested ? info.text : undefined,
|
||||
checked: info.nested ? false : hasRights(chat, mainFlag, rights),
|
||||
toggle: !isNested,
|
||||
listenerSetter: this.options.listenerSetter,
|
||||
restriction: !isNested
|
||||
}),
|
||||
listenerSetter: this.options.listenerSetter,
|
||||
clickable: info.nested ? (e) => {
|
||||
if(findUpAsChild(e.target as HTMLElement, row.checkboxField.label)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cancelEvent(e);
|
||||
row.container.classList.toggle('accordion-toggler-expanded');
|
||||
accordion.classList.toggle('is-expanded');
|
||||
} : undefined
|
||||
});
|
||||
|
||||
if((
|
||||
this.options.participant &&
|
||||
defaultBannedRights.pFlags[mainFlag as keyof typeof defaultBannedRights['pFlags']]
|
||||
) || (
|
||||
getPeerActiveUsernames(chat as Chat.channel)[0] &&
|
||||
(
|
||||
info.flags.includes('pin_messages') ||
|
||||
info.flags.includes('change_info')
|
||||
)
|
||||
)
|
||||
) {
|
||||
info.checkboxField.input.disabled = true;
|
||||
|
||||
attachClickEvent(info.checkboxField.label, (e) => {
|
||||
toast(I18n.format(restrictionText, true));
|
||||
}, {listenerSetter: this.options.listenerSetter});
|
||||
}
|
||||
|
||||
if(info.toggleWith || info.nestedTo) {
|
||||
const processToggleWith = info.toggleWith ? (info: T) => {
|
||||
const {toggleWith, nested} = info;
|
||||
const value = info.checkboxField.checked;
|
||||
const arr = value ? toggleWith.checked : toggleWith.unchecked;
|
||||
if(!arr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const other = this.v.filter((i) => arr.includes(i.flags[0]));
|
||||
other.forEach((info) => {
|
||||
info.checkboxField.setValueSilently(value);
|
||||
if(info.nestedTo && !nested) {
|
||||
this.setNestedCounter(info.nestedTo);
|
||||
}
|
||||
|
||||
cancelEvent(e);
|
||||
toast('This option is disabled for all members in Group Permissions.');
|
||||
info.checkboxField.checked = false;
|
||||
}); */
|
||||
|
||||
attachClickEvent(info.checkboxField.label, (e) => {
|
||||
toast(I18n.format(restrictionText, true));
|
||||
}, {listenerSetter: options.listenerSetter});
|
||||
}
|
||||
|
||||
if(this.toggleWith[mainFlag]) {
|
||||
options.listenerSetter.add(info.checkboxField.input)('change', () => {
|
||||
if(!info.checkboxField.checked) {
|
||||
const other = this.v.filter((i) => this.toggleWith[mainFlag].includes(i.flags[0]));
|
||||
other.forEach((info) => {
|
||||
info.checkboxField.checked = false;
|
||||
});
|
||||
if(info.toggleWith) {
|
||||
processToggleWith(info);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
options.appendTo.append(row.container);
|
||||
if(info.nested) {
|
||||
this.setNestedCounter(info);
|
||||
}
|
||||
} : undefined;
|
||||
|
||||
const processNestedTo = info.nestedTo ? () => {
|
||||
const length = this.getNestedCheckedLength(info.nestedTo);
|
||||
info.nestedTo.checkboxField.setValueSilently(length === info.nestedTo.nested.length);
|
||||
this.setNestedCounter(info.nestedTo, length);
|
||||
} : undefined;
|
||||
|
||||
this.options.listenerSetter.add(info.checkboxField.input)('change', () => {
|
||||
processToggleWith?.(info);
|
||||
processNestedTo?.();
|
||||
});
|
||||
}
|
||||
|
||||
const nodes: HTMLElement[] = [row.container];
|
||||
let accordion: HTMLElement, nestedCounter: HTMLElement;
|
||||
if(info.nested) {
|
||||
const container = accordion = document.createElement('div');
|
||||
container.classList.add('accordion');
|
||||
container.style.setProperty('--max-height', info.nested.length * 48 + 'px');
|
||||
info.nested.forEach((info) => {
|
||||
container.append(...this.createRow(info, true).nodes);
|
||||
});
|
||||
nodes.push(container);
|
||||
|
||||
const span = document.createElement('span');
|
||||
span.classList.add('tgico-down', 'accordion-icon');
|
||||
|
||||
nestedCounter = info.nestedCounter = document.createElement('b');
|
||||
this.setNestedCounter(info);
|
||||
row.title.append(' ', nestedCounter, ' ', span);
|
||||
|
||||
row.container.classList.add('accordion-toggler');
|
||||
row.titleRow.classList.add('with-delimiter');
|
||||
|
||||
row.checkboxField.setValueSilently(this.getNestedCheckedLength(info) === info.nested.length);
|
||||
}
|
||||
|
||||
return {row, nodes};
|
||||
}
|
||||
|
||||
protected getNestedCheckedLength(info: T) {
|
||||
return info.nested.reduce((acc, v) => acc + +v.checkboxField.checked, 0);
|
||||
}
|
||||
|
||||
protected setNestedCounter(info: T, count = this.getNestedCheckedLength(info)) {
|
||||
info.nestedCounter.textContent = `${count}/${info.nested.length}`;
|
||||
}
|
||||
|
||||
public takeOut() {
|
||||
|
@ -128,14 +221,23 @@ export class ChatPermissions {
|
|||
pFlags: {}
|
||||
};
|
||||
|
||||
const IGNORE_FLAGS: Set<ChatRights> = new Set([
|
||||
'send_media'
|
||||
]);
|
||||
for(const info of this.v) {
|
||||
const banned = !info.checkboxField.checked;
|
||||
if(banned) {
|
||||
info.flags.forEach((flag) => {
|
||||
// @ts-ignore
|
||||
rights.pFlags[flag] = true;
|
||||
});
|
||||
if(!banned) {
|
||||
continue;
|
||||
}
|
||||
|
||||
info.flags.forEach((flag) => {
|
||||
if(IGNORE_FLAGS.has(flag)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
rights.pFlags[flag] = true;
|
||||
});
|
||||
}
|
||||
|
||||
return rights;
|
||||
|
@ -195,7 +297,7 @@ export default class AppGroupPermissionsTab extends SliderSuperTabEventable {
|
|||
const openPermissions = async(peerId: PeerId) => {
|
||||
let participant: AppUserPermissionsTab['participant'];
|
||||
try {
|
||||
participant = await this.managers.appProfileManager.getChannelParticipant(this.chatId, peerId) as any;
|
||||
participant = await this.managers.appProfileManager.getParticipant(this.chatId, peerId);
|
||||
} catch(err) {
|
||||
toast('User is no longer participant');
|
||||
return;
|
||||
|
@ -270,37 +372,35 @@ export default class AppGroupPermissionsTab extends SliderSuperTabEventable {
|
|||
append
|
||||
});
|
||||
|
||||
setSubtitle(dom, participant);
|
||||
(dom.listEl as any).dialogDom = dom;
|
||||
|
||||
// dom.titleSpan.innerHTML = 'Chinaza Akachi';
|
||||
// dom.lastMessageSpan.innerHTML = 'Can Add Users and Pin Messages';
|
||||
setSubtitle(dom, participant);
|
||||
};
|
||||
|
||||
// this.listenerSetter.add(rootScope)('updateChannelParticipant', (update: Update.updateChannelParticipant) => {
|
||||
// const needAdd = update.new_participant?._ === 'channelParticipantBanned' && !update.new_participant.banned_rights.pFlags.view_messages;
|
||||
// const li = list.querySelector(`[data-peer-id="${update.user_id}"]`);
|
||||
// if(needAdd) {
|
||||
// if(!li) {
|
||||
// add(update.new_participant as ChannelParticipant.channelParticipantBanned, false);
|
||||
// } else {
|
||||
// setSubtitle(li, update.new_participant as ChannelParticipant.channelParticipantBanned);
|
||||
// }
|
||||
this.listenerSetter.add(rootScope)('chat_participant', (update) => {
|
||||
const needAdd = update.new_participant?._ === 'channelParticipantBanned' &&
|
||||
!update.new_participant.banned_rights.pFlags.view_messages;
|
||||
const li = list.querySelector(`[data-peer-id="${update.user_id}"]`);
|
||||
if(needAdd) {
|
||||
if(!li) {
|
||||
add(update.new_participant as ChannelParticipant.channelParticipantBanned, false);
|
||||
} else {
|
||||
setSubtitle((li as any).dialogDom, update.new_participant as ChannelParticipant.channelParticipantBanned);
|
||||
}
|
||||
|
||||
// if(update.prev_participant?._ !== 'channelParticipantBanned') {
|
||||
// ++exceptionsCount;
|
||||
// }
|
||||
// } else {
|
||||
// if(li) {
|
||||
// li.remove();
|
||||
// }
|
||||
if(update.prev_participant?._ !== 'channelParticipantBanned') {
|
||||
++exceptionsCount;
|
||||
}
|
||||
} else {
|
||||
li?.remove();
|
||||
|
||||
// if(update.prev_participant?._ === 'channelParticipantBanned') {
|
||||
// --exceptionsCount;
|
||||
// }
|
||||
// }
|
||||
if(update.prev_participant?._ === 'channelParticipantBanned') {
|
||||
--exceptionsCount;
|
||||
}
|
||||
}
|
||||
|
||||
// setLength();
|
||||
// });
|
||||
setLength();
|
||||
});
|
||||
|
||||
const setLength = () => {
|
||||
replaceContent(addExceptionRow.subtitle, i18n(exceptionsCount ? 'Permissions.ExceptionsCount' : 'Permissions.NoExceptions', [exceptionsCount]));
|
||||
|
|
|
@ -7,16 +7,18 @@
|
|||
import {attachClickEvent} from '../../../helpers/dom/clickEvent';
|
||||
import toggleDisability from '../../../helpers/dom/toggleDisability';
|
||||
import deepEqual from '../../../helpers/object/deepEqual';
|
||||
import {ChannelParticipant} from '../../../layer';
|
||||
import {ChannelParticipant, ChatParticipant} from '../../../layer';
|
||||
import appDialogsManager from '../../../lib/appManagers/appDialogsManager';
|
||||
import Button from '../../button';
|
||||
import confirmationPopup from '../../confirmationPopup';
|
||||
import SettingSection from '../../settingSection';
|
||||
import {SliderSuperTabEventable} from '../../sliderTab';
|
||||
import getUserStatusString from '../../wrappers/getUserStatusString';
|
||||
import wrapPeerTitle from '../../wrappers/peerTitle';
|
||||
import {ChatPermissions} from './groupPermissions';
|
||||
|
||||
export default class AppUserPermissionsTab extends SliderSuperTabEventable {
|
||||
public participant: ChannelParticipant;
|
||||
public participant: ChannelParticipant | ChatParticipant;
|
||||
public chatId: ChatId;
|
||||
public userId: UserId;
|
||||
|
||||
|
@ -26,6 +28,8 @@ export default class AppUserPermissionsTab extends SliderSuperTabEventable {
|
|||
|
||||
let destroyListener: () => void;
|
||||
|
||||
const isChannel = await this.managers.appChatsManager.isChannel(this.chatId);
|
||||
|
||||
{
|
||||
const section = new SettingSection({
|
||||
name: 'UserRestrictionsCanDo'
|
||||
|
@ -55,7 +59,6 @@ export default class AppUserPermissionsTab extends SliderSuperTabEventable {
|
|||
}, this.managers);
|
||||
|
||||
destroyListener = () => {
|
||||
// appChatsManager.editChatDefaultBannedRights(this.chatId, p.takeOut());
|
||||
const rights = p.takeOut();
|
||||
if(this.participant._ === 'channelParticipantBanned' && deepEqual(this.participant.banned_rights.pFlags, rights.pFlags)) {
|
||||
return;
|
||||
|
@ -77,7 +80,10 @@ export default class AppUserPermissionsTab extends SliderSuperTabEventable {
|
|||
|
||||
attachClickEvent(btnDeleteException, () => {
|
||||
const toggle = toggleDisability([btnDeleteException], true);
|
||||
this.managers.appChatsManager.clearChannelParticipantBannedRights(this.chatId, this.participant).then(() => {
|
||||
this.managers.appChatsManager.clearChannelParticipantBannedRights(
|
||||
this.chatId,
|
||||
this.participant as ChannelParticipant.channelParticipantBanned
|
||||
).then(() => {
|
||||
this.eventListener.removeEventListener('destroy', destroyListener);
|
||||
this.close();
|
||||
}, () => {
|
||||
|
@ -90,31 +96,34 @@ export default class AppUserPermissionsTab extends SliderSuperTabEventable {
|
|||
|
||||
const btnDelete = Button('btn-primary btn-transparent danger', {icon: 'deleteuser', text: 'UserRestrictionsBlock'});
|
||||
|
||||
attachClickEvent(btnDelete, () => {
|
||||
attachClickEvent(btnDelete, async() => {
|
||||
const toggle = toggleDisability([btnDelete], true);
|
||||
this.managers.appChatsManager.kickFromChannel(this.chatId, this.participant).then(() => {
|
||||
this.eventListener.removeEventListener('destroy', destroyListener);
|
||||
this.close();
|
||||
});
|
||||
/* new PopupPeer('popup-group-kick-user', {
|
||||
peerId: -this.chatId,
|
||||
title: 'Ban User?',
|
||||
description: `Are you sure you want to ban <b>${appPeersManager.getPeerTitle(this.userId)}</b>`,
|
||||
buttons: addCancelButton([{
|
||||
text: 'BAN',
|
||||
callback: () => {
|
||||
const toggle = toggleDisability([btnDelete], true);
|
||||
|
||||
appChatsManager.kickFromChannel(this.chatId, this.participant).then(() => {
|
||||
this.eventListener.removeEventListener('destroy', destroyListener);
|
||||
this.close();
|
||||
}, () => {
|
||||
toggle();
|
||||
});
|
||||
},
|
||||
isDanger: true
|
||||
}])
|
||||
}).show(); */
|
||||
try {
|
||||
const peerId = this.userId.toPeerId();
|
||||
await confirmationPopup({
|
||||
peerId: this.chatId.toPeerId(true),
|
||||
descriptionLangKey: 'Permissions.RemoveFromGroup',
|
||||
descriptionLangArgs: [await wrapPeerTitle({peerId: peerId})],
|
||||
titleLangKey: 'ChannelBlockUser',
|
||||
button: {
|
||||
langKey: 'Remove',
|
||||
isDanger: true
|
||||
}
|
||||
});
|
||||
|
||||
if(!isChannel) {
|
||||
await this.managers.appChatsManager.kickFromChat(this.chatId, this.participant);
|
||||
} else {
|
||||
await this.managers.appChatsManager.kickFromChannel(this.chatId, this.participant as ChannelParticipant);
|
||||
}
|
||||
} catch(err) {
|
||||
toggle();
|
||||
return;
|
||||
}
|
||||
|
||||
this.eventListener.removeEventListener('destroy', destroyListener);
|
||||
this.close();
|
||||
}, {listenerSetter: this.listenerSetter});
|
||||
|
||||
section.content.append(btnDelete);
|
||||
|
|
|
@ -98,7 +98,7 @@ export default class SwipeHandler {
|
|||
private onSwipe: (xDiff: number, yDiff: number, e: EE, cancelDrag?: (x: boolean, y: boolean) => void) => boolean | void;
|
||||
private verifyTouchTarget: (evt: EE) => boolean | Promise<boolean>;
|
||||
private onFirstSwipe: (e: EE) => void;
|
||||
private onReset: () => void;
|
||||
private onReset: (e?: Event) => void;
|
||||
private onStart: () => void;
|
||||
private onZoom: (details: ZoomDetails) => void;
|
||||
private onDrag: (e: EE, captureEvent: E, details: {dragOffsetX: number, dragOffsetY: number}, cancelDrag: (x: boolean, y: boolean) => void) => void;
|
||||
|
@ -252,7 +252,7 @@ export default class SwipeHandler {
|
|||
}
|
||||
|
||||
if(this.hadMove) {
|
||||
this.onReset?.();
|
||||
this.onReset?.(e);
|
||||
}
|
||||
|
||||
this.releaseWheelDrag?.clearTimeout();
|
||||
|
@ -291,10 +291,14 @@ export default class SwipeHandler {
|
|||
}
|
||||
|
||||
const e = getEvent(_e);
|
||||
if(Math.max(0, e.button ?? 0) !== 0) {
|
||||
if(![0, 1].includes(Math.max(0, e.button ?? 0))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(e.button === 1) {
|
||||
cancelEvent(_e as any);
|
||||
}
|
||||
|
||||
if(isSwipingBackSafari(_e as any)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ export default function wrapAlbum({messages, attachmentDiv, middleware, uploadin
|
|||
|
||||
// !lowest msgID will be the FIRST in album
|
||||
for(const message of messages) {
|
||||
const media: Photo.photo | Document.document = getMediaFromMessage(message);
|
||||
const media = getMediaFromMessage(message, true);
|
||||
|
||||
const size: any = media._ === 'photo' ? choosePhotoSize(media, 480, 480) : {w: media.w, h: media.h};
|
||||
items.push({size, media, message});
|
||||
|
|
|
@ -35,7 +35,7 @@ export default async function wrapPhoto({photo, message, container, boxWidth, bo
|
|||
isOut?: boolean,
|
||||
lazyLoadQueue?: LazyLoadQueue,
|
||||
middleware?: Middleware,
|
||||
size?: PhotoSize | VideoSize,
|
||||
size?: PhotoSize | Extract<VideoSize, VideoSize.videoSize>,
|
||||
withoutPreloader?: boolean,
|
||||
loadPromises?: Promise<any>[],
|
||||
autoDownloadSize?: number,
|
||||
|
|
|
@ -85,7 +85,7 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd
|
|||
skipRatio?: number,
|
||||
static?: boolean,
|
||||
managers?: AppManagers,
|
||||
fullThumb?: PhotoSize | VideoSize,
|
||||
fullThumb?: PhotoSize | Extract<VideoSize, VideoSize.videoSize>,
|
||||
isOut?: boolean,
|
||||
noPremium?: boolean,
|
||||
withLock?: boolean,
|
||||
|
|
|
@ -40,7 +40,7 @@ export default function wrapStickerAnimation({
|
|||
skipRatio?: number,
|
||||
play: boolean,
|
||||
managers?: AppManagers,
|
||||
fullThumb?: PhotoSize | VideoSize,
|
||||
fullThumb?: PhotoSize | Extract<VideoSize, VideoSize.videoSize>,
|
||||
withRandomOffset?: boolean,
|
||||
relativeEffect?: boolean,
|
||||
loopEffect?: boolean
|
||||
|
|
|
@ -82,7 +82,7 @@ export default async function wrapVideo({doc, container, message, boxWidth, boxH
|
|||
loadPromises?: Promise<any>[],
|
||||
autoDownload?: ChatAutoDownloadSettings,
|
||||
photoSize?: PhotoSize,
|
||||
videoSize?: VideoSize,
|
||||
videoSize?: Extract<VideoSize, VideoSize.videoSize>,
|
||||
searchContext?: MediaSearchContext,
|
||||
managers?: AppManagers,
|
||||
noAutoplayAttribute?: boolean
|
||||
|
|
|
@ -21,7 +21,7 @@ const App = {
|
|||
version: process.env.VERSION,
|
||||
versionFull: process.env.VERSION_FULL,
|
||||
build: +process.env.BUILD,
|
||||
langPackVersion: '0.8.6',
|
||||
langPackVersion: '0.9.3',
|
||||
langPack: 'webk',
|
||||
langPackCode: 'en',
|
||||
domains: MAIN_DOMAINS,
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
export type AUDIO_MIME_TYPE = 'audio/mpeg' | 'audio/aac' | 'audio/wav';
|
||||
const AUDIO_MIME_TYPES_SUPPORTED: Set<AUDIO_MIME_TYPE> = new Set([
|
||||
'audio/mpeg',
|
||||
'audio/aac',
|
||||
'audio/wav'
|
||||
]);
|
||||
|
||||
export default AUDIO_MIME_TYPES_SUPPORTED;
|
|
@ -56,7 +56,7 @@ declare global {
|
|||
|
||||
type LocalFileError = ApiFileManagerError | ReferenceError | StorageError;
|
||||
type LocalErrorType = LocalFileError | NetworkerError | FiltersError |
|
||||
'UNKNOWN' | 'NO_DOC' | 'MIDDLEWARE' | 'PORT_DISCONNECTED' | 'NO_AUTO_DOWNLOAD';
|
||||
'UNKNOWN' | 'NO_DOC' | 'MIDDLEWARE' | 'PORT_DISCONNECTED' | 'NO_AUTO_DOWNLOAD' | 'CHAT_PRIVATE';
|
||||
|
||||
type ServerErrorType = 'FILE_REFERENCE_EXPIRED' | 'SESSION_REVOKED' | 'AUTH_KEY_DUPLICATED' |
|
||||
'SESSION_PASSWORD_NEEDED' | 'CONNECTION_NOT_INITED' | 'ERROR_EMPTY' | 'MTPROTO_CLUSTER_INVALID' |
|
||||
|
@ -64,7 +64,8 @@ declare global {
|
|||
'VOICE_MESSAGES_FORBIDDEN' | 'PHOTO_INVALID_DIMENSIONS' | 'PHOTO_SAVE_FILE_INVALID' |
|
||||
'USER_ALREADY_PARTICIPANT' | 'USERNAME_INVALID' | 'USERNAME_PURCHASE_AVAILABLE' | 'USERNAMES_ACTIVE_TOO_MUCH' |
|
||||
'BOT_INVALID' | 'USERNAME_NOT_OCCUPIED' | 'PINNED_TOO_MUCH' | 'LOCATION_INVALID' |
|
||||
'FILE_ID_INVALID' | 'CHANNEL_FORUM_MISSING' | 'TRANSCRIPTION_FAILED';
|
||||
'FILE_ID_INVALID' | 'CHANNEL_FORUM_MISSING' | 'TRANSCRIPTION_FAILED' | 'USER_NOT_PARTICIPANT' |
|
||||
'PEER_ID_INVALID';
|
||||
|
||||
type ErrorType = LocalErrorType | ServerErrorType;
|
||||
|
||||
|
|
|
@ -8,6 +8,17 @@ import type ListenerSetter from '../listenerSetter';
|
|||
import IS_TOUCH_SUPPORTED from '../../environment/touchSupport';
|
||||
import simulateEvent from './dispatchEvent';
|
||||
|
||||
let lastMouseDownElement: HTMLElement;
|
||||
document.addEventListener('mousedown', (e) => {
|
||||
lastMouseDownElement = e.target as HTMLElement;
|
||||
});
|
||||
|
||||
export function hasMouseMovedSinceDown(e: Event) {
|
||||
if(e.isTrusted && e.type === 'click' && e.target !== lastMouseDownElement) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export const CLICK_EVENT_NAME: 'mousedown' /* | 'touchend' */ | 'click' = (IS_TOUCH_SUPPORTED ? 'mousedown' : 'click') as any;
|
||||
export type AttachClickOptions = AddEventListenerOptions & Partial<{listenerSetter: ListenerSetter, touchMouseDown: true}>;
|
||||
export function attachClickEvent(elem: HTMLElement | Window, callback: (e: /* TouchEvent | */MouseEvent) => void, options: AttachClickOptions = {}) {
|
||||
|
@ -42,6 +53,18 @@ export function attachClickEvent(elem: HTMLElement | Window, callback: (e: /* To
|
|||
} else {
|
||||
add(CLICK_EVENT_NAME, callback, options);
|
||||
} */
|
||||
|
||||
if(CLICK_EVENT_NAME === 'click') {
|
||||
const cb = callback;
|
||||
callback = (e) => {
|
||||
if(hasMouseMovedSinceDown(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
cb(e);
|
||||
};
|
||||
}
|
||||
|
||||
add(CLICK_EVENT_NAME, callback, options);
|
||||
|
||||
// @ts-ignore
|
||||
|
|
|
@ -8,7 +8,7 @@ import appNavigationController, {NavigationItem} from '../components/appNavigati
|
|||
import IS_TOUCH_SUPPORTED from '../environment/touchSupport';
|
||||
import {IS_MOBILE_SAFARI} from '../environment/userAgent';
|
||||
import cancelEvent from './dom/cancelEvent';
|
||||
import {CLICK_EVENT_NAME} from './dom/clickEvent';
|
||||
import {CLICK_EVENT_NAME, hasMouseMovedSinceDown} from './dom/clickEvent';
|
||||
import findUpAsChild from './dom/findUpAsChild';
|
||||
import EventListenerBase from './eventListenerBase';
|
||||
|
||||
|
@ -28,6 +28,10 @@ export default class OverlayClickHandler extends EventListenerBase<{
|
|||
}
|
||||
|
||||
protected onClick = (e: MouseEvent | TouchEvent) => {
|
||||
if(hasMouseMovedSinceDown(e)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.element && findUpAsChild(e.target as HTMLElement, this.element)) {
|
||||
return;
|
||||
}
|
||||
|
|
31
src/lang.ts
31
src/lang.ts
|
@ -82,6 +82,7 @@ const lang = {
|
|||
'Message.Context.Selection.Clear': 'Clear selection',
|
||||
'Message.Context.Selection.Delete': 'Delete selected',
|
||||
'Message.Context.Selection.Forward': 'Forward selected',
|
||||
'Message.Context.Selection.Download': 'Download selected',
|
||||
'Message.Context.Selection.SendNow': 'Send Now selected',
|
||||
'Message.Unsupported.Desktop': '__This message is currently not supported on Telegram Web. Try [getdesktop.telegram.org](https://getdesktop.telegram.org/)__',
|
||||
'Message.Unsupported.Mobile': '__This message is currently not supported on Telegram Web. Try [telegram.org/dl](https://telegram.org/dl/)__',
|
||||
|
@ -109,6 +110,7 @@ const lang = {
|
|||
'one_value': '%d exception',
|
||||
'other_value': '%d exceptions'
|
||||
},
|
||||
'Permissions.RemoveFromGroup': 'Are you sure you want to remove **%s** from the group?',
|
||||
'PWA.Install': 'Install App',
|
||||
'Link.Available': 'Link is available',
|
||||
'Link.Taken': 'Link is already taken',
|
||||
|
@ -400,6 +402,12 @@ const lang = {
|
|||
'UserRestrictionsSendMedia': 'Send Media',
|
||||
'UserRestrictionsSendPolls': 'Send Polls',
|
||||
'UserRestrictionsSendStickers': 'Send Stickers and GIFs',
|
||||
'UserRestrictionsSendPhotos': 'Send Photos',
|
||||
'UserRestrictionsSendVideos': 'Send Videos',
|
||||
'UserRestrictionsSendMusic': 'Send Music',
|
||||
'UserRestrictionsSendFiles': 'Send Files',
|
||||
'UserRestrictionsSendVoices': 'Send Voice Messages',
|
||||
'UserRestrictionsSendRound': 'Send Video Messages',
|
||||
'UserRestrictionsEmbedLinks': 'Embed Links',
|
||||
'UserRestrictionsChangeInfo': 'Change Chat Info',
|
||||
'UserRestrictionsPinMessages': 'Pin Messages',
|
||||
|
@ -409,6 +417,12 @@ const lang = {
|
|||
'UserRestrictionsNoSendMedia': 'no media',
|
||||
'UserRestrictionsNoSendPolls': 'no polls',
|
||||
'UserRestrictionsNoSendStickers': 'no stickers & GIFs',
|
||||
'UserRestrictionsNoSendPhotos': 'no photos',
|
||||
'UserRestrictionsNoSendVideos': 'no videos',
|
||||
'UserRestrictionsNoSendMusic': 'no music',
|
||||
'UserRestrictionsNoSendDocs': 'no files',
|
||||
'UserRestrictionsNoSendVoice': 'no voice',
|
||||
'UserRestrictionsNoSendRound': 'no round',
|
||||
'UserRestrictionsNoEmbedLinks': 'no embed links',
|
||||
'UserRestrictionsNoChangeInfo': 'can\'t change Info',
|
||||
'UserRestrictionsNoPinMessages': 'no pins',
|
||||
|
@ -892,6 +906,22 @@ const lang = {
|
|||
'ThemeDay': 'Day',
|
||||
'ThemeNight': 'Night',
|
||||
'AutoNightSystemDefault': 'System Default',
|
||||
'GlobalAttachPlainRestricted': 'Sending text messages isn\'t allowed in this group.',
|
||||
'GlobalAttachDocumentsRestricted': 'Sending documents isn\'t allowed in this group.',
|
||||
'GlobalAttachMediaRestricted': 'Sending media isn\'t allowed in this group.',
|
||||
'GlobalAttachAudioRestricted': 'Sending music isn\'t allowed in this group.',
|
||||
'GlobalAttachPhotoRestricted': 'Sending photos isn\'t allowed in this group.',
|
||||
'GlobalAttachVideoRestricted': 'Sending videos isn\'t allowed in this group.',
|
||||
'GlobalAttachVoiceRestricted': 'Sending voice isn\'t allowed in this group.',
|
||||
'GlobalAttachRoundRestricted': 'Sending round videos isn\'t allowed in this group.',
|
||||
'GlobalAttachInlineRestricted': 'Sending inline content isn\'t allowed in this group.',
|
||||
'GlobalAttachStickersRestricted': 'Stickers aren\'t allowed in this group.',
|
||||
'GlobalAttachGifRestricted': 'Sending GIFs is not allowed in this group.',
|
||||
'GlobalAttachEmojiRestricted': 'Text messages aren\'t allowed in this group.',
|
||||
'GlobalSendMessageRestricted': 'Sending messages is not allowed in this group.',
|
||||
'ErrorSendRestrictedPollsAll': 'Sorry, sending polls is not allowed in this group.',
|
||||
'Remove': 'Remove',
|
||||
'ChannelBlockUser': 'Remove User',
|
||||
|
||||
// * macos
|
||||
'AccountSettings.Filters': 'Chat Folders',
|
||||
|
@ -1110,6 +1140,7 @@ const lang = {
|
|||
'ChatList.Mute.3Days': 'For 3 Days',
|
||||
'ChatList.Mute.Forever': 'Forever',
|
||||
'Channel.DescriptionHolderDescrpiton': 'You can provide an optional description for your channel.',
|
||||
'Channel.Persmission.MessageBlock': 'Text is not Allowed',
|
||||
'ChannelVisibility.Confirm.MakePrivate.Channel': 'If you make this channel private, the name @%@ will be removed. Anyone else will be able to take it for their public groups or channels.',
|
||||
'ChannelVisibility.Confirm.MakePrivate.Group': 'If you make this group private, the name @%@ will be removed. Anyone else will be able to take it for their public groups or channels.',
|
||||
'Context.ViewStickerSet': 'View Sticker Set',
|
||||
|
|
|
@ -285,7 +285,8 @@ export namespace InputChatPhoto {
|
|||
flags?: number,
|
||||
file?: InputFile,
|
||||
video?: InputFile,
|
||||
video_start_ts?: number
|
||||
video_start_ts?: number,
|
||||
video_emoji_markup?: VideoSize
|
||||
};
|
||||
|
||||
export type inputChatPhoto = {
|
||||
|
@ -699,6 +700,7 @@ export namespace ChatFull {
|
|||
pFlags: Partial<{
|
||||
can_set_username?: true,
|
||||
has_scheduled?: true,
|
||||
translations_disabled?: true,
|
||||
}>,
|
||||
id: string | number,
|
||||
about: string,
|
||||
|
@ -733,6 +735,7 @@ export namespace ChatFull {
|
|||
can_delete_channel?: true,
|
||||
antispam?: true,
|
||||
participants_hidden?: true,
|
||||
translations_disabled?: true,
|
||||
}>,
|
||||
flags2?: number,
|
||||
id: string | number,
|
||||
|
@ -1071,7 +1074,7 @@ export namespace MessageMedia {
|
|||
/**
|
||||
* @link https://core.telegram.org/type/MessageAction
|
||||
*/
|
||||
export type MessageAction = MessageAction.messageActionEmpty | MessageAction.messageActionChatCreate | MessageAction.messageActionChatEditTitle | MessageAction.messageActionChatEditPhoto | MessageAction.messageActionChatDeletePhoto | MessageAction.messageActionChatAddUser | MessageAction.messageActionChatDeleteUser | MessageAction.messageActionChatJoinedByLink | MessageAction.messageActionChannelCreate | MessageAction.messageActionChatMigrateTo | MessageAction.messageActionChannelMigrateFrom | MessageAction.messageActionPinMessage | MessageAction.messageActionHistoryClear | MessageAction.messageActionGameScore | MessageAction.messageActionPaymentSentMe | MessageAction.messageActionPaymentSent | MessageAction.messageActionPhoneCall | MessageAction.messageActionScreenshotTaken | MessageAction.messageActionCustomAction | MessageAction.messageActionBotAllowed | MessageAction.messageActionSecureValuesSentMe | MessageAction.messageActionSecureValuesSent | MessageAction.messageActionContactSignUp | MessageAction.messageActionGeoProximityReached | MessageAction.messageActionGroupCall | MessageAction.messageActionInviteToGroupCall | MessageAction.messageActionSetMessagesTTL | MessageAction.messageActionGroupCallScheduled | MessageAction.messageActionSetChatTheme | MessageAction.messageActionChatJoinedByRequest | MessageAction.messageActionWebViewDataSentMe | MessageAction.messageActionWebViewDataSent | MessageAction.messageActionGiftPremium | MessageAction.messageActionTopicCreate | MessageAction.messageActionTopicEdit | MessageAction.messageActionSuggestProfilePhoto | MessageAction.messageActionAttachMenuBotAllowed | MessageAction.messageActionDiscussionStarted | MessageAction.messageActionChatLeave | MessageAction.messageActionChannelDeletePhoto | MessageAction.messageActionChannelEditTitle | MessageAction.messageActionChannelEditPhoto | MessageAction.messageActionChannelEditVideo | MessageAction.messageActionChatEditVideo | MessageAction.messageActionChatAddUsers | MessageAction.messageActionChatJoined | MessageAction.messageActionChatReturn | MessageAction.messageActionChatJoinedYou | MessageAction.messageActionChatReturnYou;
|
||||
export type MessageAction = MessageAction.messageActionEmpty | MessageAction.messageActionChatCreate | MessageAction.messageActionChatEditTitle | MessageAction.messageActionChatEditPhoto | MessageAction.messageActionChatDeletePhoto | MessageAction.messageActionChatAddUser | MessageAction.messageActionChatDeleteUser | MessageAction.messageActionChatJoinedByLink | MessageAction.messageActionChannelCreate | MessageAction.messageActionChatMigrateTo | MessageAction.messageActionChannelMigrateFrom | MessageAction.messageActionPinMessage | MessageAction.messageActionHistoryClear | MessageAction.messageActionGameScore | MessageAction.messageActionPaymentSentMe | MessageAction.messageActionPaymentSent | MessageAction.messageActionPhoneCall | MessageAction.messageActionScreenshotTaken | MessageAction.messageActionCustomAction | MessageAction.messageActionBotAllowed | MessageAction.messageActionSecureValuesSentMe | MessageAction.messageActionSecureValuesSent | MessageAction.messageActionContactSignUp | MessageAction.messageActionGeoProximityReached | MessageAction.messageActionGroupCall | MessageAction.messageActionInviteToGroupCall | MessageAction.messageActionSetMessagesTTL | MessageAction.messageActionGroupCallScheduled | MessageAction.messageActionSetChatTheme | MessageAction.messageActionChatJoinedByRequest | MessageAction.messageActionWebViewDataSentMe | MessageAction.messageActionWebViewDataSent | MessageAction.messageActionGiftPremium | MessageAction.messageActionTopicCreate | MessageAction.messageActionTopicEdit | MessageAction.messageActionSuggestProfilePhoto | MessageAction.messageActionAttachMenuBotAllowed | MessageAction.messageActionRequestedPeer | MessageAction.messageActionDiscussionStarted | MessageAction.messageActionChatLeave | MessageAction.messageActionChannelDeletePhoto | MessageAction.messageActionChannelEditTitle | MessageAction.messageActionChannelEditPhoto | MessageAction.messageActionChannelEditVideo | MessageAction.messageActionChatEditVideo | MessageAction.messageActionChatAddUsers | MessageAction.messageActionChatJoined | MessageAction.messageActionChatReturn | MessageAction.messageActionChatJoinedYou | MessageAction.messageActionChatReturnYou;
|
||||
|
||||
export namespace MessageAction {
|
||||
export type messageActionEmpty = {
|
||||
|
@ -1296,6 +1299,12 @@ export namespace MessageAction {
|
|||
_: 'messageActionAttachMenuBotAllowed'
|
||||
};
|
||||
|
||||
export type messageActionRequestedPeer = {
|
||||
_: 'messageActionRequestedPeer',
|
||||
button_id: number,
|
||||
peer: Peer
|
||||
};
|
||||
|
||||
export type messageActionDiscussionStarted = {
|
||||
_: 'messageActionDiscussionStarted'
|
||||
};
|
||||
|
@ -1527,7 +1536,7 @@ export namespace GeoPoint {
|
|||
/**
|
||||
* @link https://core.telegram.org/type/auth.SentCode
|
||||
*/
|
||||
export type AuthSentCode = AuthSentCode.authSentCode;
|
||||
export type AuthSentCode = AuthSentCode.authSentCode | AuthSentCode.authSentCodeSuccess;
|
||||
|
||||
export namespace AuthSentCode {
|
||||
export type authSentCode = {
|
||||
|
@ -1539,6 +1548,11 @@ export namespace AuthSentCode {
|
|||
timeout?: number,
|
||||
phone_number?: string
|
||||
};
|
||||
|
||||
export type authSentCodeSuccess = {
|
||||
_: 'auth.sentCodeSuccess',
|
||||
authorization: AuthAuthorization
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1555,6 +1569,7 @@ export namespace AuthAuthorization {
|
|||
}>,
|
||||
otherwise_relogin_days?: number,
|
||||
tmp_sessions?: number,
|
||||
future_auth_token?: Uint8Array,
|
||||
user: User
|
||||
};
|
||||
|
||||
|
@ -1766,6 +1781,7 @@ export namespace UserFull {
|
|||
has_scheduled?: true,
|
||||
video_calls_available?: true,
|
||||
voice_messages_forbidden?: true,
|
||||
translations_disabled?: true,
|
||||
}>,
|
||||
id: string | number,
|
||||
about?: string,
|
||||
|
@ -2086,7 +2102,7 @@ export namespace MessagesFilter {
|
|||
/**
|
||||
* @link https://core.telegram.org/type/Update
|
||||
*/
|
||||
export type Update = Update.updateNewMessage | Update.updateMessageID | Update.updateDeleteMessages | Update.updateUserTyping | Update.updateChatUserTyping | Update.updateChatParticipants | Update.updateUserStatus | Update.updateUserName | Update.updateNewEncryptedMessage | Update.updateEncryptedChatTyping | Update.updateEncryption | Update.updateEncryptedMessagesRead | Update.updateChatParticipantAdd | Update.updateChatParticipantDelete | Update.updateDcOptions | Update.updateNotifySettings | Update.updateServiceNotification | Update.updatePrivacy | Update.updateUserPhone | Update.updateReadHistoryInbox | Update.updateReadHistoryOutbox | Update.updateWebPage | Update.updateReadMessagesContents | Update.updateChannelTooLong | Update.updateChannel | Update.updateNewChannelMessage | Update.updateReadChannelInbox | Update.updateDeleteChannelMessages | Update.updateChannelMessageViews | Update.updateChatParticipantAdmin | Update.updateNewStickerSet | Update.updateStickerSetsOrder | Update.updateStickerSets | Update.updateSavedGifs | Update.updateBotInlineQuery | Update.updateBotInlineSend | Update.updateEditChannelMessage | Update.updateBotCallbackQuery | Update.updateEditMessage | Update.updateInlineBotCallbackQuery | Update.updateReadChannelOutbox | Update.updateDraftMessage | Update.updateReadFeaturedStickers | Update.updateRecentStickers | Update.updateConfig | Update.updatePtsChanged | Update.updateChannelWebPage | Update.updateDialogPinned | Update.updatePinnedDialogs | Update.updateBotWebhookJSON | Update.updateBotWebhookJSONQuery | Update.updateBotShippingQuery | Update.updateBotPrecheckoutQuery | Update.updatePhoneCall | Update.updateLangPackTooLong | Update.updateLangPack | Update.updateFavedStickers | Update.updateChannelReadMessagesContents | Update.updateContactsReset | Update.updateChannelAvailableMessages | Update.updateDialogUnreadMark | Update.updateMessagePoll | Update.updateChatDefaultBannedRights | Update.updateFolderPeers | Update.updatePeerSettings | Update.updatePeerLocated | Update.updateNewScheduledMessage | Update.updateDeleteScheduledMessages | Update.updateTheme | Update.updateGeoLiveViewed | Update.updateLoginToken | Update.updateMessagePollVote | Update.updateDialogFilter | Update.updateDialogFilterOrder | Update.updateDialogFilters | Update.updatePhoneCallSignalingData | Update.updateChannelMessageForwards | Update.updateReadChannelDiscussionInbox | Update.updateReadChannelDiscussionOutbox | Update.updatePeerBlocked | Update.updateChannelUserTyping | Update.updatePinnedMessages | Update.updatePinnedChannelMessages | Update.updateChat | Update.updateGroupCallParticipants | Update.updateGroupCall | Update.updatePeerHistoryTTL | Update.updateChatParticipant | Update.updateChannelParticipant | Update.updateBotStopped | Update.updateGroupCallConnection | Update.updateBotCommands | Update.updatePendingJoinRequests | Update.updateBotChatInviteRequester | Update.updateMessageReactions | Update.updateAttachMenuBots | Update.updateWebViewResultSent | Update.updateBotMenuButton | Update.updateSavedRingtones | Update.updateTranscribedAudio | Update.updateReadFeaturedEmojiStickers | Update.updateUserEmojiStatus | Update.updateRecentEmojiStatuses | Update.updateRecentReactions | Update.updateMoveStickerSetToTop | Update.updateMessageExtendedMedia | Update.updateChannelPinnedTopic | Update.updateChannelPinnedTopics | Update.updateUser | Update.updateNewDiscussionMessage | Update.updateDeleteDiscussionMessages | Update.updateChannelReload;
|
||||
export type Update = Update.updateNewMessage | Update.updateMessageID | Update.updateDeleteMessages | Update.updateUserTyping | Update.updateChatUserTyping | Update.updateChatParticipants | Update.updateUserStatus | Update.updateUserName | Update.updateNewEncryptedMessage | Update.updateEncryptedChatTyping | Update.updateEncryption | Update.updateEncryptedMessagesRead | Update.updateChatParticipantAdd | Update.updateChatParticipantDelete | Update.updateDcOptions | Update.updateNotifySettings | Update.updateServiceNotification | Update.updatePrivacy | Update.updateUserPhone | Update.updateReadHistoryInbox | Update.updateReadHistoryOutbox | Update.updateWebPage | Update.updateReadMessagesContents | Update.updateChannelTooLong | Update.updateChannel | Update.updateNewChannelMessage | Update.updateReadChannelInbox | Update.updateDeleteChannelMessages | Update.updateChannelMessageViews | Update.updateChatParticipantAdmin | Update.updateNewStickerSet | Update.updateStickerSetsOrder | Update.updateStickerSets | Update.updateSavedGifs | Update.updateBotInlineQuery | Update.updateBotInlineSend | Update.updateEditChannelMessage | Update.updateBotCallbackQuery | Update.updateEditMessage | Update.updateInlineBotCallbackQuery | Update.updateReadChannelOutbox | Update.updateDraftMessage | Update.updateReadFeaturedStickers | Update.updateRecentStickers | Update.updateConfig | Update.updatePtsChanged | Update.updateChannelWebPage | Update.updateDialogPinned | Update.updatePinnedDialogs | Update.updateBotWebhookJSON | Update.updateBotWebhookJSONQuery | Update.updateBotShippingQuery | Update.updateBotPrecheckoutQuery | Update.updatePhoneCall | Update.updateLangPackTooLong | Update.updateLangPack | Update.updateFavedStickers | Update.updateChannelReadMessagesContents | Update.updateContactsReset | Update.updateChannelAvailableMessages | Update.updateDialogUnreadMark | Update.updateMessagePoll | Update.updateChatDefaultBannedRights | Update.updateFolderPeers | Update.updatePeerSettings | Update.updatePeerLocated | Update.updateNewScheduledMessage | Update.updateDeleteScheduledMessages | Update.updateTheme | Update.updateGeoLiveViewed | Update.updateLoginToken | Update.updateMessagePollVote | Update.updateDialogFilter | Update.updateDialogFilterOrder | Update.updateDialogFilters | Update.updatePhoneCallSignalingData | Update.updateChannelMessageForwards | Update.updateReadChannelDiscussionInbox | Update.updateReadChannelDiscussionOutbox | Update.updatePeerBlocked | Update.updateChannelUserTyping | Update.updatePinnedMessages | Update.updatePinnedChannelMessages | Update.updateChat | Update.updateGroupCallParticipants | Update.updateGroupCall | Update.updatePeerHistoryTTL | Update.updateChatParticipant | Update.updateChannelParticipant | Update.updateBotStopped | Update.updateGroupCallConnection | Update.updateBotCommands | Update.updatePendingJoinRequests | Update.updateBotChatInviteRequester | Update.updateMessageReactions | Update.updateAttachMenuBots | Update.updateWebViewResultSent | Update.updateBotMenuButton | Update.updateSavedRingtones | Update.updateTranscribedAudio | Update.updateReadFeaturedEmojiStickers | Update.updateUserEmojiStatus | Update.updateRecentEmojiStatuses | Update.updateRecentReactions | Update.updateMoveStickerSetToTop | Update.updateMessageExtendedMedia | Update.updateChannelPinnedTopic | Update.updateChannelPinnedTopics | Update.updateUser | Update.updateAutoSaveSettings | Update.updateNewDiscussionMessage | Update.updateDeleteDiscussionMessages | Update.updateChannelReload;
|
||||
|
||||
export namespace Update {
|
||||
export type updateNewMessage = {
|
||||
|
@ -2868,6 +2884,10 @@ export namespace Update {
|
|||
user_id: string | number
|
||||
};
|
||||
|
||||
export type updateAutoSaveSettings = {
|
||||
_: 'updateAutoSaveSettings'
|
||||
};
|
||||
|
||||
export type updateNewDiscussionMessage = {
|
||||
_: 'updateNewDiscussionMessage',
|
||||
message?: Message
|
||||
|
@ -4325,7 +4345,7 @@ export namespace BotInfo {
|
|||
/**
|
||||
* @link https://core.telegram.org/type/KeyboardButton
|
||||
*/
|
||||
export type KeyboardButton = KeyboardButton.keyboardButton | KeyboardButton.keyboardButtonUrl | KeyboardButton.keyboardButtonCallback | KeyboardButton.keyboardButtonRequestPhone | KeyboardButton.keyboardButtonRequestGeoLocation | KeyboardButton.keyboardButtonSwitchInline | KeyboardButton.keyboardButtonGame | KeyboardButton.keyboardButtonBuy | KeyboardButton.keyboardButtonUrlAuth | KeyboardButton.inputKeyboardButtonUrlAuth | KeyboardButton.keyboardButtonRequestPoll | KeyboardButton.inputKeyboardButtonUserProfile | KeyboardButton.keyboardButtonUserProfile | KeyboardButton.keyboardButtonWebView | KeyboardButton.keyboardButtonSimpleWebView;
|
||||
export type KeyboardButton = KeyboardButton.keyboardButton | KeyboardButton.keyboardButtonUrl | KeyboardButton.keyboardButtonCallback | KeyboardButton.keyboardButtonRequestPhone | KeyboardButton.keyboardButtonRequestGeoLocation | KeyboardButton.keyboardButtonSwitchInline | KeyboardButton.keyboardButtonGame | KeyboardButton.keyboardButtonBuy | KeyboardButton.keyboardButtonUrlAuth | KeyboardButton.inputKeyboardButtonUrlAuth | KeyboardButton.keyboardButtonRequestPoll | KeyboardButton.inputKeyboardButtonUserProfile | KeyboardButton.keyboardButtonUserProfile | KeyboardButton.keyboardButtonWebView | KeyboardButton.keyboardButtonSimpleWebView | KeyboardButton.keyboardButtonRequestPeer;
|
||||
|
||||
export namespace KeyboardButton {
|
||||
export type keyboardButton = {
|
||||
|
@ -4430,6 +4450,13 @@ export namespace KeyboardButton {
|
|||
text: string,
|
||||
url: string
|
||||
};
|
||||
|
||||
export type keyboardButtonRequestPeer = {
|
||||
_: 'keyboardButtonRequestPeer',
|
||||
text: string,
|
||||
button_id: number,
|
||||
peer_type: RequestPeerType
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -5268,7 +5295,7 @@ export namespace AuthCodeType {
|
|||
/**
|
||||
* @link https://core.telegram.org/type/auth.SentCodeType
|
||||
*/
|
||||
export type AuthSentCodeType = AuthSentCodeType.authSentCodeTypeApp | AuthSentCodeType.authSentCodeTypeSms | AuthSentCodeType.authSentCodeTypeCall | AuthSentCodeType.authSentCodeTypeFlashCall | AuthSentCodeType.authSentCodeTypeMissedCall | AuthSentCodeType.authSentCodeTypeEmailCode | AuthSentCodeType.authSentCodeTypeSetUpEmailRequired | AuthSentCodeType.authSentCodeTypeFragmentSms;
|
||||
export type AuthSentCodeType = AuthSentCodeType.authSentCodeTypeApp | AuthSentCodeType.authSentCodeTypeSms | AuthSentCodeType.authSentCodeTypeCall | AuthSentCodeType.authSentCodeTypeFlashCall | AuthSentCodeType.authSentCodeTypeMissedCall | AuthSentCodeType.authSentCodeTypeEmailCode | AuthSentCodeType.authSentCodeTypeSetUpEmailRequired | AuthSentCodeType.authSentCodeTypeFragmentSms | AuthSentCodeType.authSentCodeTypeFirebaseSms;
|
||||
|
||||
export namespace AuthSentCodeType {
|
||||
export type authSentCodeTypeApp = {
|
||||
|
@ -5323,6 +5350,15 @@ export namespace AuthSentCodeType {
|
|||
url: string,
|
||||
length: number
|
||||
};
|
||||
|
||||
export type authSentCodeTypeFirebaseSms = {
|
||||
_: 'auth.sentCodeTypeFirebaseSms',
|
||||
flags?: number,
|
||||
nonce?: Uint8Array,
|
||||
receipt?: string,
|
||||
push_timeout?: number,
|
||||
length: number
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -8090,6 +8126,13 @@ export namespace ChatBannedRights {
|
|||
invite_users?: true,
|
||||
pin_messages?: true,
|
||||
manage_topics?: true,
|
||||
send_photos?: true,
|
||||
send_videos?: true,
|
||||
send_roundvideos?: true,
|
||||
send_audios?: true,
|
||||
send_voices?: true,
|
||||
send_docs?: true,
|
||||
send_plain?: true,
|
||||
}>,
|
||||
until_date: number
|
||||
};
|
||||
|
@ -8149,8 +8192,11 @@ export namespace CodeSettings {
|
|||
current_number?: true,
|
||||
allow_app_hash?: true,
|
||||
allow_missed_call?: true,
|
||||
allow_firebase?: true,
|
||||
}>,
|
||||
logout_tokens?: Array<Uint8Array>
|
||||
logout_tokens?: Array<Uint8Array>,
|
||||
token?: string,
|
||||
app_sandbox?: boolean
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -8857,7 +8903,7 @@ export namespace HelpPromoData {
|
|||
/**
|
||||
* @link https://core.telegram.org/type/VideoSize
|
||||
*/
|
||||
export type VideoSize = VideoSize.videoSize;
|
||||
export type VideoSize = VideoSize.videoSize | VideoSize.videoSizeEmojiMarkup | VideoSize.videoSizeStickerMarkup;
|
||||
|
||||
export namespace VideoSize {
|
||||
export type videoSize = {
|
||||
|
@ -8869,6 +8915,19 @@ export namespace VideoSize {
|
|||
size: number,
|
||||
video_start_ts?: number
|
||||
};
|
||||
|
||||
export type videoSizeEmojiMarkup = {
|
||||
_: 'videoSizeEmojiMarkup',
|
||||
emoji_id: string | number,
|
||||
background_colors: Array<number>
|
||||
};
|
||||
|
||||
export type videoSizeStickerMarkup = {
|
||||
_: 'videoSizeStickerMarkup',
|
||||
stickerset: InputStickerSet,
|
||||
sticker_id: string | number,
|
||||
background_colors: Array<number>
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -9798,22 +9857,6 @@ export namespace MessagesAvailableReactions {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://core.telegram.org/type/messages.TranslatedText
|
||||
*/
|
||||
export type MessagesTranslatedText = MessagesTranslatedText.messagesTranslateNoResult | MessagesTranslatedText.messagesTranslateResultText;
|
||||
|
||||
export namespace MessagesTranslatedText {
|
||||
export type messagesTranslateNoResult = {
|
||||
_: 'messages.translateNoResult'
|
||||
};
|
||||
|
||||
export type messagesTranslateResultText = {
|
||||
_: 'messages.translateResultText',
|
||||
text: string
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://core.telegram.org/type/MessagePeerReaction
|
||||
*/
|
||||
|
@ -10171,6 +10214,7 @@ export namespace InputStorePaymentPurpose {
|
|||
flags?: number,
|
||||
pFlags: Partial<{
|
||||
restore?: true,
|
||||
upgrade?: true,
|
||||
}>
|
||||
};
|
||||
|
||||
|
@ -10212,6 +10256,19 @@ export namespace PaymentFormMethod {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://core.telegram.org/type/bots.PremiumGiftsOptions
|
||||
*/
|
||||
export type BotsPremiumGiftsOptions = BotsPremiumGiftsOptions.botsPremiumGiftsOptions;
|
||||
|
||||
export namespace BotsPremiumGiftsOptions {
|
||||
export type botsPremiumGiftsOptions = {
|
||||
_: 'bots.premiumGiftsOptions',
|
||||
name: string,
|
||||
gifts: Array<PremiumGiftOption>
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://core.telegram.org/type/EmojiStatus
|
||||
*/
|
||||
|
@ -10387,6 +10444,7 @@ export namespace PremiumSubscriptionOption {
|
|||
current?: true,
|
||||
can_purchase_upgrade?: true,
|
||||
}>,
|
||||
transaction?: string,
|
||||
months: number,
|
||||
currency: string,
|
||||
amount: string | number,
|
||||
|
@ -10549,6 +10607,164 @@ export namespace ExportedContactToken {
|
|||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://core.telegram.org/type/RequestPeerType
|
||||
*/
|
||||
export type RequestPeerType = RequestPeerType.requestPeerTypeUser | RequestPeerType.requestPeerTypeChat | RequestPeerType.requestPeerTypeBroadcast;
|
||||
|
||||
export namespace RequestPeerType {
|
||||
export type requestPeerTypeUser = {
|
||||
_: 'requestPeerTypeUser',
|
||||
flags?: number,
|
||||
bot?: boolean,
|
||||
premium?: boolean
|
||||
};
|
||||
|
||||
export type requestPeerTypeChat = {
|
||||
_: 'requestPeerTypeChat',
|
||||
flags?: number,
|
||||
pFlags: Partial<{
|
||||
creator?: true,
|
||||
bot_participant?: true,
|
||||
}>,
|
||||
has_username?: boolean,
|
||||
forum?: boolean,
|
||||
user_admin_rights?: ChatAdminRights,
|
||||
bot_admin_rights?: ChatAdminRights
|
||||
};
|
||||
|
||||
export type requestPeerTypeBroadcast = {
|
||||
_: 'requestPeerTypeBroadcast',
|
||||
flags?: number,
|
||||
pFlags: Partial<{
|
||||
creator?: true,
|
||||
}>,
|
||||
has_username?: boolean,
|
||||
user_admin_rights?: ChatAdminRights,
|
||||
bot_admin_rights?: ChatAdminRights
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://core.telegram.org/type/EmojiList
|
||||
*/
|
||||
export type EmojiList = EmojiList.emojiListNotModified | EmojiList.emojiList;
|
||||
|
||||
export namespace EmojiList {
|
||||
export type emojiListNotModified = {
|
||||
_: 'emojiListNotModified'
|
||||
};
|
||||
|
||||
export type emojiList = {
|
||||
_: 'emojiList',
|
||||
hash: string | number,
|
||||
document_id: Array<string | number>
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://core.telegram.org/type/EmojiGroup
|
||||
*/
|
||||
export type EmojiGroup = EmojiGroup.emojiGroup;
|
||||
|
||||
export namespace EmojiGroup {
|
||||
export type emojiGroup = {
|
||||
_: 'emojiGroup',
|
||||
title: string,
|
||||
icon_emoji_id: string | number,
|
||||
emoticons: Array<string>
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://core.telegram.org/type/messages.EmojiGroups
|
||||
*/
|
||||
export type MessagesEmojiGroups = MessagesEmojiGroups.messagesEmojiGroupsNotModified | MessagesEmojiGroups.messagesEmojiGroups;
|
||||
|
||||
export namespace MessagesEmojiGroups {
|
||||
export type messagesEmojiGroupsNotModified = {
|
||||
_: 'messages.emojiGroupsNotModified'
|
||||
};
|
||||
|
||||
export type messagesEmojiGroups = {
|
||||
_: 'messages.emojiGroups',
|
||||
hash: number,
|
||||
groups: Array<EmojiGroup>
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://core.telegram.org/type/TextWithEntities
|
||||
*/
|
||||
export type TextWithEntities = TextWithEntities.textWithEntities;
|
||||
|
||||
export namespace TextWithEntities {
|
||||
export type textWithEntities = {
|
||||
_: 'textWithEntities',
|
||||
text: string,
|
||||
entities: Array<MessageEntity>
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://core.telegram.org/type/messages.TranslatedText
|
||||
*/
|
||||
export type MessagesTranslatedText = MessagesTranslatedText.messagesTranslateResult;
|
||||
|
||||
export namespace MessagesTranslatedText {
|
||||
export type messagesTranslateResult = {
|
||||
_: 'messages.translateResult',
|
||||
result: Array<TextWithEntities>
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://core.telegram.org/type/AutoSaveSettings
|
||||
*/
|
||||
export type AutoSaveSettings = AutoSaveSettings.autoSaveSettings;
|
||||
|
||||
export namespace AutoSaveSettings {
|
||||
export type autoSaveSettings = {
|
||||
_: 'autoSaveSettings',
|
||||
flags?: number,
|
||||
pFlags: Partial<{
|
||||
photos?: true,
|
||||
videos?: true,
|
||||
}>,
|
||||
video_max_size?: string | number
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://core.telegram.org/type/AutoSaveException
|
||||
*/
|
||||
export type AutoSaveException = AutoSaveException.autoSaveException;
|
||||
|
||||
export namespace AutoSaveException {
|
||||
export type autoSaveException = {
|
||||
_: 'autoSaveException',
|
||||
peer: Peer,
|
||||
settings: AutoSaveSettings
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @link https://core.telegram.org/type/account.AutoSaveSettings
|
||||
*/
|
||||
export type AccountAutoSaveSettings = AccountAutoSaveSettings.accountAutoSaveSettings;
|
||||
|
||||
export namespace AccountAutoSaveSettings {
|
||||
export type accountAutoSaveSettings = {
|
||||
_: 'account.autoSaveSettings',
|
||||
users_settings: AutoSaveSettings,
|
||||
chats_settings: AutoSaveSettings,
|
||||
broadcasts_settings: AutoSaveSettings,
|
||||
exceptions: Array<AutoSaveException>,
|
||||
chats: Array<Chat>,
|
||||
users: Array<User>
|
||||
};
|
||||
}
|
||||
|
||||
export interface ConstructorDeclMap {
|
||||
'error': Error.error,
|
||||
'inputPeerEmpty': InputPeer.inputPeerEmpty,
|
||||
|
@ -11495,8 +11711,6 @@ export interface ConstructorDeclMap {
|
|||
'messages.availableReactions': MessagesAvailableReactions.messagesAvailableReactions,
|
||||
'messageEntitySpoiler': MessageEntity.messageEntitySpoiler,
|
||||
'channelAdminLogEventActionChangeAvailableReactions': ChannelAdminLogEventAction.channelAdminLogEventActionChangeAvailableReactions,
|
||||
'messages.translateNoResult': MessagesTranslatedText.messagesTranslateNoResult,
|
||||
'messages.translateResultText': MessagesTranslatedText.messagesTranslateResultText,
|
||||
'messagePeerReaction': MessagePeerReaction.messagePeerReaction,
|
||||
'groupCallStreamChannel': GroupCallStreamChannel.groupCallStreamChannel,
|
||||
'phone.groupCallStreamChannels': PhoneGroupCallStreamChannels.phoneGroupCallStreamChannels,
|
||||
|
@ -11557,6 +11771,7 @@ export interface ConstructorDeclMap {
|
|||
'privacyKeyVoiceMessages': PrivacyKey.privacyKeyVoiceMessages,
|
||||
'paymentFormMethod': PaymentFormMethod.paymentFormMethod,
|
||||
'inputWebFileAudioAlbumThumbLocation': InputWebFileLocation.inputWebFileAudioAlbumThumbLocation,
|
||||
'bots.premiumGiftsOptions': BotsPremiumGiftsOptions.botsPremiumGiftsOptions,
|
||||
'emojiStatusEmpty': EmojiStatus.emojiStatusEmpty,
|
||||
'emojiStatus': EmojiStatus.emojiStatus,
|
||||
'emojiStatusUntil': EmojiStatus.emojiStatusUntil,
|
||||
|
@ -11619,6 +11834,26 @@ export interface ConstructorDeclMap {
|
|||
'messageActionAttachMenuBotAllowed': MessageAction.messageActionAttachMenuBotAllowed,
|
||||
'stickerSetNoCovered': StickerSetCovered.stickerSetNoCovered,
|
||||
'updateUser': Update.updateUser,
|
||||
'auth.sentCodeSuccess': AuthSentCode.authSentCodeSuccess,
|
||||
'messageActionRequestedPeer': MessageAction.messageActionRequestedPeer,
|
||||
'requestPeerTypeUser': RequestPeerType.requestPeerTypeUser,
|
||||
'requestPeerTypeChat': RequestPeerType.requestPeerTypeChat,
|
||||
'requestPeerTypeBroadcast': RequestPeerType.requestPeerTypeBroadcast,
|
||||
'keyboardButtonRequestPeer': KeyboardButton.keyboardButtonRequestPeer,
|
||||
'emojiListNotModified': EmojiList.emojiListNotModified,
|
||||
'emojiList': EmojiList.emojiList,
|
||||
'auth.sentCodeTypeFirebaseSms': AuthSentCodeType.authSentCodeTypeFirebaseSms,
|
||||
'emojiGroup': EmojiGroup.emojiGroup,
|
||||
'messages.emojiGroupsNotModified': MessagesEmojiGroups.messagesEmojiGroupsNotModified,
|
||||
'messages.emojiGroups': MessagesEmojiGroups.messagesEmojiGroups,
|
||||
'videoSizeEmojiMarkup': VideoSize.videoSizeEmojiMarkup,
|
||||
'videoSizeStickerMarkup': VideoSize.videoSizeStickerMarkup,
|
||||
'textWithEntities': TextWithEntities.textWithEntities,
|
||||
'messages.translateResult': MessagesTranslatedText.messagesTranslateResult,
|
||||
'autoSaveSettings': AutoSaveSettings.autoSaveSettings,
|
||||
'autoSaveException': AutoSaveException.autoSaveException,
|
||||
'account.autoSaveSettings': AccountAutoSaveSettings.accountAutoSaveSettings,
|
||||
'updateAutoSaveSettings': Update.updateAutoSaveSettings,
|
||||
'messageEntityEmoji': MessageEntity.messageEntityEmoji,
|
||||
'messageEntityHighlight': MessageEntity.messageEntityHighlight,
|
||||
'messageEntityLinebreak': MessageEntity.messageEntityLinebreak,
|
||||
|
@ -11998,7 +12233,8 @@ export type PhotosUploadProfilePhoto = {
|
|||
fallback?: boolean,
|
||||
file?: InputFile,
|
||||
video?: InputFile,
|
||||
video_start_ts?: number
|
||||
video_start_ts?: number,
|
||||
video_emoji_markup?: VideoSize
|
||||
};
|
||||
|
||||
export type PhotosDeletePhotos = {
|
||||
|
@ -12353,6 +12589,7 @@ export type ChannelsCreateChannel = {
|
|||
broadcast?: boolean,
|
||||
megagroup?: boolean,
|
||||
for_import?: boolean,
|
||||
forum?: boolean,
|
||||
title: string,
|
||||
about: string,
|
||||
geo_point?: InputGeoPoint,
|
||||
|
@ -12807,6 +13044,8 @@ export type StickersCreateStickerSet = {
|
|||
masks?: boolean,
|
||||
animated?: boolean,
|
||||
videos?: boolean,
|
||||
emojis?: boolean,
|
||||
text_color?: boolean,
|
||||
user_id: InputUser,
|
||||
title: string,
|
||||
short_name: string,
|
||||
|
@ -14034,9 +14273,8 @@ export type MessagesSetDefaultReaction = {
|
|||
export type MessagesTranslateText = {
|
||||
flags?: number,
|
||||
peer?: InputPeer,
|
||||
msg_id?: number,
|
||||
text?: string,
|
||||
from_lang?: string,
|
||||
id?: Array<number>,
|
||||
text?: Array<TextWithEntities>,
|
||||
to_lang: string
|
||||
};
|
||||
|
||||
|
@ -14392,7 +14630,8 @@ export type PhotosUploadContactProfilePhoto = {
|
|||
user_id: InputUser,
|
||||
file?: InputFile,
|
||||
video?: InputFile,
|
||||
video_start_ts?: number
|
||||
video_start_ts?: number,
|
||||
video_emoji_markup?: VideoSize
|
||||
};
|
||||
|
||||
export type ChannelsToggleParticipantsHidden = {
|
||||
|
@ -14400,6 +14639,69 @@ export type ChannelsToggleParticipantsHidden = {
|
|||
enabled: boolean
|
||||
};
|
||||
|
||||
export type MessagesSendBotRequestedPeer = {
|
||||
peer: InputPeer,
|
||||
msg_id: number,
|
||||
button_id: number,
|
||||
requested_peer: InputPeer
|
||||
};
|
||||
|
||||
export type AccountGetDefaultProfilePhotoEmojis = {
|
||||
hash: string | number
|
||||
};
|
||||
|
||||
export type AccountGetDefaultGroupPhotoEmojis = {
|
||||
hash: string | number
|
||||
};
|
||||
|
||||
export type AuthRequestFirebaseSms = {
|
||||
flags?: number,
|
||||
phone_number: string,
|
||||
phone_code_hash: string,
|
||||
safety_net_token?: string,
|
||||
ios_push_secret?: string
|
||||
};
|
||||
|
||||
export type MessagesGetEmojiGroups = {
|
||||
hash: number
|
||||
};
|
||||
|
||||
export type MessagesGetEmojiStatusGroups = {
|
||||
hash: number
|
||||
};
|
||||
|
||||
export type MessagesGetEmojiProfilePhotoGroups = {
|
||||
hash: number
|
||||
};
|
||||
|
||||
export type MessagesSearchCustomEmoji = {
|
||||
emoticon: string,
|
||||
hash: string | number
|
||||
};
|
||||
|
||||
export type MessagesTogglePeerTranslations = {
|
||||
flags?: number,
|
||||
disabled?: boolean,
|
||||
peer: InputPeer
|
||||
};
|
||||
|
||||
export type AccountGetAutoSaveSettings = {
|
||||
|
||||
};
|
||||
|
||||
export type AccountSaveAutoSaveSettings = {
|
||||
flags?: number,
|
||||
users?: boolean,
|
||||
chats?: boolean,
|
||||
broadcasts?: boolean,
|
||||
peer?: InputPeer,
|
||||
settings: AutoSaveSettings
|
||||
};
|
||||
|
||||
export type AccountDeleteAutoSaveExceptions = {
|
||||
|
||||
};
|
||||
|
||||
export interface MethodDeclMap {
|
||||
'invokeAfterMsg': {req: InvokeAfterMsg, res: any},
|
||||
'invokeAfterMsgs': {req: InvokeAfterMsgs, res: any},
|
||||
|
@ -14879,5 +15181,17 @@ export interface MethodDeclMap {
|
|||
'contacts.importContactToken': {req: ContactsImportContactToken, res: User},
|
||||
'photos.uploadContactProfilePhoto': {req: PhotosUploadContactProfilePhoto, res: PhotosPhoto},
|
||||
'channels.toggleParticipantsHidden': {req: ChannelsToggleParticipantsHidden, res: Updates},
|
||||
'messages.sendBotRequestedPeer': {req: MessagesSendBotRequestedPeer, res: Updates},
|
||||
'account.getDefaultProfilePhotoEmojis': {req: AccountGetDefaultProfilePhotoEmojis, res: EmojiList},
|
||||
'account.getDefaultGroupPhotoEmojis': {req: AccountGetDefaultGroupPhotoEmojis, res: EmojiList},
|
||||
'auth.requestFirebaseSms': {req: AuthRequestFirebaseSms, res: boolean},
|
||||
'messages.getEmojiGroups': {req: MessagesGetEmojiGroups, res: MessagesEmojiGroups},
|
||||
'messages.getEmojiStatusGroups': {req: MessagesGetEmojiStatusGroups, res: MessagesEmojiGroups},
|
||||
'messages.getEmojiProfilePhotoGroups': {req: MessagesGetEmojiProfilePhotoGroups, res: MessagesEmojiGroups},
|
||||
'messages.searchCustomEmoji': {req: MessagesSearchCustomEmoji, res: EmojiList},
|
||||
'messages.togglePeerTranslations': {req: MessagesTogglePeerTranslations, res: boolean},
|
||||
'account.getAutoSaveSettings': {req: AccountGetAutoSaveSettings, res: AccountAutoSaveSettings},
|
||||
'account.saveAutoSaveSettings': {req: AccountSaveAutoSaveSettings, res: boolean},
|
||||
'account.deleteAutoSaveExceptions': {req: AccountDeleteAutoSaveExceptions, res: boolean},
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import parseMarkdown from '../richTextProcessor/parseMarkdown';
|
|||
import ctx from '../../environment/ctx';
|
||||
import EventListenerBase from '../../helpers/eventListenerBase';
|
||||
import applyMixins from '../../helpers/applyMixins';
|
||||
import tsNow from '../../helpers/tsNow';
|
||||
|
||||
type UpdatesState = {
|
||||
pendingPtsUpdates: (Update & {pts: number, pts_count: number})[],
|
||||
|
@ -311,7 +312,7 @@ class ApiUpdatesManager {
|
|||
updatesState.date = nextState.date;
|
||||
} else {
|
||||
updatesState.pts = differenceResult.pts;
|
||||
updatesState.date = (Date.now() / 1000 | 0) + this.timeManager.getServerTimeOffset();
|
||||
updatesState.date = tsNow(true) + this.timeManager.getServerTimeOffset();
|
||||
delete updatesState.seq;
|
||||
|
||||
this.channelStates = {};
|
||||
|
@ -740,7 +741,7 @@ class ApiUpdatesManager {
|
|||
message,
|
||||
type: 'local',
|
||||
pFlags: {},
|
||||
inbox_date: Date.now() / 1000 | 0,
|
||||
inbox_date: tsNow(true),
|
||||
media: undefined
|
||||
};
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
import deepEqual from '../../helpers/object/deepEqual';
|
||||
import isObject from '../../helpers/object/isObject';
|
||||
import safeReplaceObject from '../../helpers/object/safeReplaceObject';
|
||||
import {ChannelParticipant, ChannelsCreateChannel, Chat, ChatAdminRights, ChatBannedRights, ChatInvite, ChatPhoto, ChatReactions, InputChannel, InputChatPhoto, InputFile, InputPeer, MessagesSponsoredMessages, SponsoredMessage, Update, Updates} from '../../layer';
|
||||
import {ChannelParticipant, ChannelsCreateChannel, Chat, ChatAdminRights, ChatBannedRights, ChatInvite, ChatParticipant, ChatPhoto, ChatReactions, InputChannel, InputChatPhoto, InputFile, InputPeer, MessagesSponsoredMessages, SponsoredMessage, Update, Updates} from '../../layer';
|
||||
import {isRestricted} from '../../helpers/restrictions';
|
||||
import {AppManager} from './manager';
|
||||
import hasRights from './utils/chats/hasRights';
|
||||
|
@ -21,6 +21,7 @@ import {AppStoragesManager} from './appStoragesManager';
|
|||
import getServerMessageId from './utils/messageId/getServerMessageId';
|
||||
import {randomLong} from '../../helpers/random';
|
||||
import generateMessageId from './utils/messageId/generateMessageId';
|
||||
import tsNow from '../../helpers/tsNow';
|
||||
|
||||
export type Channel = Chat.channel;
|
||||
export type ChatRights = keyof ChatBannedRights['pFlags'] | keyof ChatAdminRights['pFlags'] | 'change_type' | 'change_permissions' | 'delete_chat' | 'view_participants';
|
||||
|
@ -37,26 +38,9 @@ export class AppChatsManager extends AppManager {
|
|||
this.clear(true);
|
||||
|
||||
this.apiUpdatesManager.addMultipleEventsListeners({
|
||||
/* updateChannel: (update) => {
|
||||
const channelId = update.channel_id;
|
||||
//console.log('updateChannel:', update);
|
||||
rootScope.broadcast('channel_settings', {channelId});
|
||||
}, */
|
||||
updateChannelParticipant: this.onUpdateChannelParticipant,
|
||||
|
||||
updateChannelParticipant: (update) => {
|
||||
this.apiManager.clearCache('channels.getParticipants', (params) => {
|
||||
return (params.channel as InputChannel.inputChannel).channel_id === update.channel_id;
|
||||
});
|
||||
},
|
||||
|
||||
updateChatDefaultBannedRights: (update) => {
|
||||
const chatId = this.appPeersManager.getPeerId(update.peer).toChatId();
|
||||
const chat = this.chats[chatId] as Chat.chat;
|
||||
if(chat) {
|
||||
chat.default_banned_rights = update.default_banned_rights;
|
||||
this.rootScope.dispatchEvent('chat_update', chatId);
|
||||
}
|
||||
}
|
||||
updateChatDefaultBannedRights: this.onUpdateChatDefaultBannedRights
|
||||
});
|
||||
|
||||
return Promise.all([
|
||||
|
@ -535,8 +519,14 @@ export class AppChatsManager extends AppManager {
|
|||
});
|
||||
}
|
||||
|
||||
public editBanned(id: ChatId, participant: PeerId | ChannelParticipant, banned_rights: ChatBannedRights) {
|
||||
public async editBanned(id: ChatId, participant: PeerId | ChannelParticipant | ChatParticipant, banned_rights: ChatBannedRights) {
|
||||
const peerId = typeof(participant) !== 'object' ? participant : getParticipantPeerId(participant);
|
||||
const wasChannel = this.isChannel(id);
|
||||
if(!wasChannel) {
|
||||
const channelId = await this.migrateChat(id);
|
||||
id = channelId;
|
||||
}
|
||||
|
||||
return this.apiManager.invokeApi('channels.editBanned', {
|
||||
channel: this.getChannelInput(id),
|
||||
participant: this.appPeersManager.getInputPeerById(peerId),
|
||||
|
@ -545,7 +535,7 @@ export class AppChatsManager extends AppManager {
|
|||
this.onChatUpdated(id, updates);
|
||||
|
||||
if(typeof(participant) === 'object') {
|
||||
const timestamp = Date.now() / 1000 | 0;
|
||||
const timestamp = tsNow(true);
|
||||
this.apiUpdatesManager.processLocalUpdate({
|
||||
_: 'updateChannelParticipant',
|
||||
channel_id: id,
|
||||
|
@ -553,7 +543,7 @@ export class AppChatsManager extends AppManager {
|
|||
actor_id: undefined,
|
||||
qts: undefined,
|
||||
user_id: peerId,
|
||||
prev_participant: participant,
|
||||
prev_participant: wasChannel ? participant as ChannelParticipant : undefined,
|
||||
new_participant: Object.keys(banned_rights.pFlags).length ? {
|
||||
_: 'channelParticipantBanned',
|
||||
date: timestamp,
|
||||
|
@ -585,9 +575,9 @@ export class AppChatsManager extends AppManager {
|
|||
});
|
||||
}
|
||||
|
||||
public kickFromChat(id: ChatId, participant: PeerId | ChannelParticipant) {
|
||||
if(this.isChannel(id)) return this.kickFromChannel(id, participant);
|
||||
else return this.deleteChatUser(id, (participant as PeerId).toUserId());
|
||||
public kickFromChat(id: ChatId, participant: PeerId | ChannelParticipant | ChatParticipant) {
|
||||
if(this.isChannel(id)) return this.kickFromChannel(id, participant as ChannelParticipant);
|
||||
else return this.deleteChatUser(id, isObject(participant) ? getParticipantPeerId(participant) : (participant as PeerId).toUserId());
|
||||
}
|
||||
|
||||
public resolveChannel(id: ChatId) {
|
||||
|
@ -904,4 +894,21 @@ export class AppChatsManager extends AppManager {
|
|||
pinned
|
||||
}).then(this.onChatUpdated.bind(this, chatId));
|
||||
}
|
||||
|
||||
private onUpdateChannelParticipant = (update: Update.updateChannelParticipant) => {
|
||||
this.apiManager.clearCache('channels.getParticipants', (params) => {
|
||||
return (params.channel as InputChannel.inputChannel).channel_id === update.channel_id;
|
||||
});
|
||||
|
||||
this.rootScope.dispatchEvent('chat_participant', update);
|
||||
};
|
||||
|
||||
private onUpdateChatDefaultBannedRights = (update: Update.updateChatDefaultBannedRights) => {
|
||||
const chatId = this.appPeersManager.getPeerId(update.peer).toChatId();
|
||||
const chat = this.chats[chatId] as Chat.chat;
|
||||
if(chat) {
|
||||
chat.default_banned_rights = update.default_banned_rights;
|
||||
this.rootScope.dispatchEvent('chat_update', chatId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -221,7 +221,7 @@ type DialogElementOptions = {
|
|||
wrapOptions?: WrapSomethingOptions,
|
||||
isMainList?: boolean
|
||||
};
|
||||
class DialogElement extends Row {
|
||||
export class DialogElement extends Row {
|
||||
public dom: DialogDom;
|
||||
|
||||
constructor({
|
||||
|
@ -2748,7 +2748,7 @@ export class AppDialogsManager {
|
|||
let mediaContainer: HTMLElement;
|
||||
const willPrepend: (Promise<any> | HTMLElement)[] = [];
|
||||
if(lastMessage && !draftMessage && !isRestricted) {
|
||||
const media: MyDocument | MyPhoto = getMediaFromMessage(lastMessage);
|
||||
const media = getMediaFromMessage(lastMessage, true);
|
||||
const videoTypes: Set<MyDocument['type']> = new Set(['video', 'gif', 'round']);
|
||||
if(media && (media._ === 'photo' || videoTypes.has(media.type))) {
|
||||
const size = choosePhotoSize(media, 20, 20);
|
||||
|
|
|
@ -26,6 +26,7 @@ import getDocumentURL from './utils/docs/getDocumentURL';
|
|||
import makeError from '../../helpers/makeError';
|
||||
import {EXTENSION_MIME_TYPE_MAP} from '../../environment/mimeTypeMap';
|
||||
import {THUMB_TYPE_FULL} from '../mtproto/mtproto_config';
|
||||
import tsNow from '../../helpers/tsNow';
|
||||
|
||||
export type MyDocument = Document.document;
|
||||
|
||||
|
@ -331,7 +332,7 @@ export class AppDocsManager extends AppManager {
|
|||
id,
|
||||
mime_type: file.type as MTMimeType,
|
||||
size: file.size,
|
||||
date: Date.now() / 1000,
|
||||
date: tsNow(true),
|
||||
pFlags: {},
|
||||
thumbs: [thumb],
|
||||
file_name: file.name
|
||||
|
|
|
@ -239,7 +239,7 @@ export class AppDraftsManager extends AppManager {
|
|||
public setDraft(peerId: PeerId, threadId: number, message: string, entities?: MessageEntity[]) {
|
||||
const draft: DraftMessage.draftMessage = {
|
||||
_: 'draftMessage',
|
||||
date: Date.now() / 1000 | 0,
|
||||
date: tsNow(true),
|
||||
message,
|
||||
pFlags: {},
|
||||
entities
|
||||
|
|
|
@ -45,6 +45,7 @@ import debounce from '../../helpers/schedulers/debounce';
|
|||
import pause from '../../helpers/schedulers/pause';
|
||||
import {InternalLink, InternalLinkTypeMap, INTERNAL_LINK_TYPE} from './internalLink';
|
||||
import MEDIA_MIME_TYPES_SUPPORTED from '../../environment/mediaMimeTypesSupport';
|
||||
import IMAGE_MIME_TYPES_SUPPORTED from '../../environment/imageMimeTypesSupport';
|
||||
import {NULL_PEER_ID} from '../mtproto/mtproto_config';
|
||||
import telegramMeWebManager from '../mtproto/telegramMeWebManager';
|
||||
import {ONE_DAY} from '../../helpers/date';
|
||||
|
@ -102,6 +103,8 @@ import findUpTag from '../../helpers/dom/findUpTag';
|
|||
import {MTAppConfig} from '../mtproto/appConfig';
|
||||
import PopupForward from '../../components/popups/forward';
|
||||
import AppBackgroundTab from '../../components/sidebarLeft/tabs/background';
|
||||
import partition from '../../helpers/array/partition';
|
||||
import indexOfAndSplice from '../../helpers/array/indexOfAndSplice';
|
||||
|
||||
export type ChatSavedPosition = {
|
||||
mids: number[],
|
||||
|
@ -1728,24 +1731,43 @@ export class AppImManager extends EventListenerBase<{
|
|||
return;
|
||||
}
|
||||
|
||||
const rights = await PopupNewMedia.canSend(this.chat.peerId, true);
|
||||
|
||||
const _dropsContainer = newMediaPopup ? mediaDropsContainer : dropsContainer;
|
||||
const _drops = newMediaPopup ? mediaDrops : drops;
|
||||
|
||||
if(mount && !_drops.length) {
|
||||
const force = isFiles && !types.length; // * can't get file items not from 'drop' on Safari
|
||||
|
||||
const foundMedia = types.filter((t) => MEDIA_MIME_TYPES_SUPPORTED.has(t)).length;
|
||||
// const foundDocuments = types.length - foundMedia;
|
||||
const [foundMedia, foundDocuments] = partition(types, (t) => MEDIA_MIME_TYPES_SUPPORTED.has(t));
|
||||
const [foundPhotos, foundVideos] = partition(foundMedia, (t) => IMAGE_MIME_TYPES_SUPPORTED.has(t));
|
||||
|
||||
this.log('drag files', types);
|
||||
if(!rights.send_docs) {
|
||||
foundDocuments.length = 0;
|
||||
} else {
|
||||
foundDocuments.push(...foundMedia);
|
||||
}
|
||||
|
||||
if(!rights.send_photos) {
|
||||
foundPhotos.forEach((mimeType) => indexOfAndSplice(foundMedia, mimeType));
|
||||
foundPhotos.length = 0;
|
||||
}
|
||||
|
||||
if(!rights.send_videos) {
|
||||
foundVideos.forEach((mimeType) => indexOfAndSplice(foundMedia, mimeType));
|
||||
foundVideos.length = 0;
|
||||
}
|
||||
|
||||
this.log('drag files', types, foundMedia, foundDocuments, foundPhotos, foundVideos);
|
||||
|
||||
if(newMediaPopup) {
|
||||
newMediaPopup.appendDrops(_dropsContainer);
|
||||
|
||||
if(types.length || force) {
|
||||
const length = (rights.send_docs ? [foundDocuments] : [foundPhotos, foundVideos]).reduce((acc, v) => acc + v.length, 0);
|
||||
if(length || force) {
|
||||
_drops.push(new ChatDragAndDrop(_dropsContainer, {
|
||||
header: 'Preview.Dragging.AddItems',
|
||||
headerArgs: [types.length],
|
||||
headerArgs: [length],
|
||||
onDrop: (e: DragEvent) => {
|
||||
toggle(e, false);
|
||||
this.log('drop', e);
|
||||
|
@ -1754,7 +1776,7 @@ export class AppImManager extends EventListenerBase<{
|
|||
}));
|
||||
}
|
||||
} else {
|
||||
if(types.length || force) {
|
||||
if(foundDocuments.length || force) {
|
||||
_drops.push(new ChatDragAndDrop(_dropsContainer, {
|
||||
icon: 'dragfiles',
|
||||
header: 'Chat.DropTitle',
|
||||
|
@ -1767,8 +1789,7 @@ export class AppImManager extends EventListenerBase<{
|
|||
}));
|
||||
}
|
||||
|
||||
// if((foundMedia && !foundDocuments) || force) {
|
||||
if(foundMedia || force) {
|
||||
if(foundMedia.length || force) {
|
||||
_drops.push(new ChatDragAndDrop(_dropsContainer, {
|
||||
icon: 'dragmedia',
|
||||
header: 'Chat.DropTitle',
|
||||
|
|
|
@ -524,7 +524,7 @@ export class AppMessagesManager extends AppManager {
|
|||
});
|
||||
}
|
||||
|
||||
public transcribeAudio(message: Message.message): Promise<MessagesTranscribedAudio> {
|
||||
public async transcribeAudio(message: Message.message): Promise<MessagesTranscribedAudio> {
|
||||
const {id, peerId} = message;
|
||||
|
||||
const process = (result: MessagesTranscribedAudio) => {
|
||||
|
@ -3916,16 +3916,12 @@ export class AppMessagesManager extends AppManager {
|
|||
});
|
||||
}
|
||||
|
||||
public async deleteMessages(peerId: PeerId, mids: number[], revoke?: boolean) {
|
||||
public async deleteMessages(peerId: PeerId, mids: number[], revoke?: boolean, isRecursion?: boolean) {
|
||||
let promise: Promise<any>;
|
||||
|
||||
const config = await this.apiManager.getConfig();
|
||||
const overflowMids = mids.splice(config.forwarded_count_max, mids.length - config.forwarded_count_max);
|
||||
|
||||
const localMessageIds = mids.map((mid) => getServerMessageId(mid));
|
||||
|
||||
if(peerId.isAnyChat() && this.appPeersManager.isChannel(peerId)) {
|
||||
const channelId = peerId.toChatId();
|
||||
const isChannel = this.appPeersManager.isChannel(peerId);
|
||||
const channelId = isChannel && peerId.toChatId();
|
||||
if(isChannel && !isRecursion) {
|
||||
const channel = this.appChatsManager.getChat(channelId) as Chat.channel;
|
||||
if(!channel.pFlags.creator && !channel.admin_rights?.pFlags?.delete_messages) {
|
||||
mids = mids.filter((mid) => {
|
||||
|
@ -3937,10 +3933,21 @@ export class AppMessagesManager extends AppManager {
|
|||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const config = await this.apiManager.getConfig();
|
||||
const overflowMids = mids.splice(config.forwarded_count_max, mids.length - config.forwarded_count_max);
|
||||
|
||||
const serverMessageIds = mids.map((mid) => {
|
||||
const messageId = getServerMessageId(mid);
|
||||
// filter outgoing messages
|
||||
return generateMessageId(messageId) === mid && messageId;
|
||||
}).filter(Boolean);
|
||||
|
||||
if(isChannel) {
|
||||
promise = this.apiManager.invokeApi('channels.deleteMessages', {
|
||||
channel: this.appChatsManager.getChannelInput(channelId),
|
||||
id: localMessageIds
|
||||
id: serverMessageIds
|
||||
}).then((affectedMessages) => {
|
||||
this.apiUpdatesManager.processLocalUpdate({
|
||||
_: 'updateDeleteChannelMessages',
|
||||
|
@ -3953,7 +3960,7 @@ export class AppMessagesManager extends AppManager {
|
|||
} else {
|
||||
promise = this.apiManager.invokeApi('messages.deleteMessages', {
|
||||
revoke,
|
||||
id: localMessageIds
|
||||
id: serverMessageIds
|
||||
}).then((affectedMessages) => {
|
||||
this.apiUpdatesManager.processLocalUpdate({
|
||||
_: 'updateDeleteMessages',
|
||||
|
@ -3966,7 +3973,7 @@ export class AppMessagesManager extends AppManager {
|
|||
|
||||
const promises: (typeof promise)[] = [promise];
|
||||
if(overflowMids.length) {
|
||||
promises.push(this.deleteMessages(peerId, overflowMids, revoke));
|
||||
promises.push(this.deleteMessages(peerId, overflowMids, revoke, true));
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(noop);
|
||||
|
|
|
@ -20,6 +20,8 @@ import getParticipantPeerId from './utils/chats/getParticipantPeerId';
|
|||
import ctx from '../../environment/ctx';
|
||||
import {ReferenceContext} from '../mtproto/referenceDatabase';
|
||||
import generateMessageId from './utils/messageId/generateMessageId';
|
||||
import assumeType from '../../helpers/assumeType';
|
||||
import makeError from '../../helpers/makeError';
|
||||
|
||||
export type UserTyping = Partial<{userId: UserId, action: SendMessageAction, timeout: number}>;
|
||||
|
||||
|
@ -34,56 +36,11 @@ export class AppProfileManager extends AppManager {
|
|||
|
||||
protected after() {
|
||||
this.apiUpdatesManager.addMultipleEventsListeners({
|
||||
updateChatParticipants: (update) => {
|
||||
const participants = update.participants;
|
||||
if(participants._ === 'chatParticipants') {
|
||||
const chatId = participants.chat_id;
|
||||
const chatFull = this.chatsFull[chatId] as ChatFull.chatFull;
|
||||
if(chatFull !== undefined) {
|
||||
chatFull.participants = participants;
|
||||
this.rootScope.dispatchEvent('chat_full_update', chatId);
|
||||
}
|
||||
}
|
||||
},
|
||||
updateChatParticipants: this.onUpdateChatParticipants,
|
||||
|
||||
updateChatParticipantAdd: (update) => {
|
||||
const chatFull = this.chatsFull[update.chat_id] as ChatFull.chatFull;
|
||||
if(chatFull !== undefined) {
|
||||
const _participants = chatFull.participants as ChatParticipants.chatParticipants;
|
||||
const participants = _participants.participants || [];
|
||||
for(let i = 0, length = participants.length; i < length; i++) {
|
||||
if(participants[i].user_id === update.user_id) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
updateChatParticipantAdd: this.onUpdateChatParticipantAdd,
|
||||
|
||||
participants.push({
|
||||
_: 'chatParticipant',
|
||||
user_id: update.user_id,
|
||||
inviter_id: update.inviter_id,
|
||||
date: tsNow(true)
|
||||
});
|
||||
|
||||
_participants.version = update.version;
|
||||
this.rootScope.dispatchEvent('chat_full_update', update.chat_id);
|
||||
}
|
||||
},
|
||||
|
||||
updateChatParticipantDelete: (update) => {
|
||||
const chatFull = this.chatsFull[update.chat_id] as ChatFull.chatFull;
|
||||
if(chatFull !== undefined) {
|
||||
const _participants = chatFull.participants as ChatParticipants.chatParticipants;
|
||||
const participants = _participants.participants || [];
|
||||
for(let i = 0, length = participants.length; i < length; i++) {
|
||||
if(participants[i].user_id === update.user_id) {
|
||||
participants.splice(i, 1);
|
||||
_participants.version = update.version;
|
||||
this.rootScope.dispatchEvent('chat_full_update', update.chat_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
updateChatParticipantDelete: this.onUpdateChatParticipantDelete,
|
||||
|
||||
updateUserTyping: this.onUpdateUserTyping,
|
||||
updateChatUserTyping: this.onUpdateUserTyping,
|
||||
|
@ -332,7 +289,68 @@ export class AppProfileManager extends AppManager {
|
|||
});
|
||||
}
|
||||
|
||||
public getChannelParticipants(id: ChatId, filter: ChannelParticipantsFilter = {_: 'channelParticipantsRecent'}, limit = 200, offset = 0) {
|
||||
public getParticipants(
|
||||
id: ChatId,
|
||||
filter: ChannelParticipantsFilter = {_: 'channelParticipantsRecent'},
|
||||
limit = 200,
|
||||
offset = 0
|
||||
) {
|
||||
if(this.appChatsManager.isChannel(id)) {
|
||||
return this.getChannelParticipants(id, filter, limit, offset);
|
||||
}
|
||||
|
||||
return Promise.resolve(this.getChatFull(id)).then((chatFull) => {
|
||||
const chatParticipants = (chatFull as ChatFull.chatFull).participants;
|
||||
if(chatParticipants._ !== 'chatParticipants') {
|
||||
throw makeError('CHAT_PRIVATE');
|
||||
}
|
||||
|
||||
if(filter._ === 'channelParticipantsSearch' && filter.q.trim()) {
|
||||
const index = this.appUsersManager.createSearchIndex();
|
||||
chatParticipants.participants.forEach((chatParticipant) => {
|
||||
const userId = chatParticipant.user_id;
|
||||
index.indexObject(userId, this.appUsersManager.getUserSearchText(userId));
|
||||
});
|
||||
|
||||
const found = index.search(filter.q);
|
||||
const filteredParticipants = chatParticipants.participants.filter((chatParticipant) => {
|
||||
return found.has(chatParticipant.user_id);
|
||||
});
|
||||
|
||||
return {...chatParticipants, participants: filteredParticipants};
|
||||
}
|
||||
|
||||
return chatParticipants;
|
||||
});
|
||||
}
|
||||
|
||||
public getParticipant(id: ChatId, peerId: PeerId) {
|
||||
if(this.appChatsManager.isChannel(id)) {
|
||||
return this.getChannelParticipant(id, peerId);
|
||||
}
|
||||
|
||||
return this.getParticipants(id).then((chatParticipants) => {
|
||||
assumeType<ChatParticipants.chatParticipants>(chatParticipants);
|
||||
const found = chatParticipants.participants.find((chatParticipant) => {
|
||||
if(getParticipantPeerId(chatParticipant) === peerId) {
|
||||
return chatParticipant;
|
||||
}
|
||||
});
|
||||
|
||||
if(!found) {
|
||||
throw makeError('USER_NOT_PARTICIPANT');
|
||||
}
|
||||
|
||||
return found;
|
||||
});
|
||||
}
|
||||
|
||||
public getChannelParticipants(
|
||||
id: ChatId,
|
||||
filter: ChannelParticipantsFilter = {_: 'channelParticipantsRecent'},
|
||||
limit = 200,
|
||||
offset = 0
|
||||
) {
|
||||
if(filter._ === 'channelParticipantsRecent') {
|
||||
const chat = this.appChatsManager.getChat(id);
|
||||
if(chat?.pFlags && (
|
||||
|
@ -647,6 +665,67 @@ export class AppProfileManager extends AppManager {
|
|||
return peerId + (threadId ? `_${threadId}` : '');
|
||||
}
|
||||
|
||||
public getPeerTypings(peerId: PeerId, threadId?: number) {
|
||||
return this.typingsInPeer[this.getTypingsKey(peerId, threadId)];
|
||||
}
|
||||
|
||||
private onUpdateChatParticipants = (update: Update.updateChatParticipants) => {
|
||||
const participants = update.participants;
|
||||
if(participants._ !== 'chatParticipants') {
|
||||
return;
|
||||
}
|
||||
|
||||
const chatId = participants.chat_id;
|
||||
const chatFull = this.chatsFull[chatId] as ChatFull.chatFull;
|
||||
if(chatFull !== undefined) {
|
||||
chatFull.participants = participants;
|
||||
this.rootScope.dispatchEvent('chat_full_update', chatId);
|
||||
}
|
||||
};
|
||||
|
||||
private onUpdateChatParticipantAdd = (update: Update.updateChatParticipantAdd) => {
|
||||
const chatFull = this.chatsFull[update.chat_id] as ChatFull.chatFull;
|
||||
if(chatFull === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const _participants = chatFull.participants as ChatParticipants.chatParticipants;
|
||||
const participants = _participants.participants || [];
|
||||
for(let i = 0, length = participants.length; i < length; i++) {
|
||||
if(participants[i].user_id === update.user_id) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
participants.push({
|
||||
_: 'chatParticipant',
|
||||
user_id: update.user_id,
|
||||
inviter_id: update.inviter_id,
|
||||
date: tsNow(true)
|
||||
});
|
||||
|
||||
_participants.version = update.version;
|
||||
this.rootScope.dispatchEvent('chat_full_update', update.chat_id);
|
||||
};
|
||||
|
||||
private onUpdateChatParticipantDelete = (update: Update.updateChatParticipantDelete) => {
|
||||
const chatFull = this.chatsFull[update.chat_id] as ChatFull.chatFull;
|
||||
if(chatFull === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const _participants = chatFull.participants as ChatParticipants.chatParticipants;
|
||||
const participants = _participants.participants || [];
|
||||
for(let i = 0, length = participants.length; i < length; i++) {
|
||||
if(participants[i].user_id === update.user_id) {
|
||||
participants.splice(i, 1);
|
||||
_participants.version = update.version;
|
||||
this.rootScope.dispatchEvent('chat_full_update', update.chat_id);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private onUpdateUserTyping = (update: Update.updateUserTyping | Update.updateChatUserTyping | Update.updateChannelUserTyping) => {
|
||||
const fromId = (update as Update.updateUserTyping).user_id ?
|
||||
(update as Update.updateUserTyping).user_id.toPeerId() :
|
||||
|
@ -741,8 +820,4 @@ export class AppProfileManager extends AppManager {
|
|||
|
||||
this.rootScope.dispatchEvent('peer_block', {peerId, blocked: update.blocked});
|
||||
};
|
||||
|
||||
public getPeerTypings(peerId: PeerId, threadId?: number) {
|
||||
return this.typingsInPeer[this.getTypingsKey(peerId, threadId)];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import type {MyDocument} from './appDocsManager';
|
||||
import type {DownloadOptions} from '../mtproto/apiFileManager';
|
||||
import {Document, InputFileLocation, InputStickerSet, MessagesAllStickers, MessagesFavedStickers, MessagesFeaturedStickers, MessagesFoundStickerSets, MessagesRecentStickers, MessagesStickers, MessagesStickerSet, PhotoSize, StickerPack, StickerSet, StickerSetCovered, Update} from '../../layer';
|
||||
import {Document, InputFileLocation, InputStickerSet, MessagesAllStickers, MessagesFavedStickers, MessagesFeaturedStickers, MessagesFoundStickerSets, MessagesRecentStickers, MessagesStickers, MessagesStickerSet, PhotoSize, StickerPack, StickerSet, StickerSetCovered, Update, VideoSize} from '../../layer';
|
||||
import {Modify} from '../../types';
|
||||
import AppStorage from '../storage';
|
||||
import DATABASE_STATE from '../../config/databases/state';
|
||||
|
@ -20,6 +20,7 @@ import ctx from '../../environment/ctx';
|
|||
import {getEnvironment} from '../../environment/utils';
|
||||
import getDocumentInput from './utils/docs/getDocumentInput';
|
||||
import getStickerEffectThumb from './utils/stickers/getStickerEffectThumb';
|
||||
import tsNow from '../../helpers/tsNow';
|
||||
|
||||
const CACHE_TIME = 3600e3;
|
||||
|
||||
|
@ -406,7 +407,7 @@ export class AppStickersManager extends AppManager {
|
|||
|
||||
public preloadSticker(docId: DocId, isPremiumEffect?: boolean) {
|
||||
const doc = this.appDocsManager.getDoc(docId);
|
||||
return this.apiFileManager.downloadMedia({media: doc, thumb: isPremiumEffect ? doc.video_thumbs?.[0] : undefined});
|
||||
return this.apiFileManager.downloadMedia({media: doc, thumb: isPremiumEffect ? doc.video_thumbs?.[0] as Extract<VideoSize, VideoSize.videoSize> : undefined});
|
||||
}
|
||||
|
||||
private saveStickerSet(res: Omit<MessagesStickerSet.messagesStickerSet, '_'>, id: DocId) {
|
||||
|
@ -592,7 +593,7 @@ export class AppStickersManager extends AppManager {
|
|||
});
|
||||
|
||||
if(res) {
|
||||
set.installed_date = Date.now() / 1000 | 0;
|
||||
set.installed_date = tsNow(true);
|
||||
this.rootScope.dispatchEvent('stickers_installed', set);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -431,7 +431,7 @@ export class AppUsersManager extends AppManager {
|
|||
return index.search(query).has(user.id);
|
||||
}
|
||||
|
||||
private createSearchIndex() {
|
||||
public createSearchIndex() {
|
||||
return new SearchIndex<UserId>({
|
||||
clearBadChars: true,
|
||||
ignoreCase: true,
|
||||
|
|
|
@ -35,8 +35,8 @@ import DEBUG from '../../config/debug';
|
|||
|
||||
// const key2 = [('00000' + sentCount).slice(-5), key].join('-');
|
||||
|
||||
// let byManager = stats[manager] ??= {};
|
||||
// let byMethod = byManager[method] ??= {times: [], byArgs: {}};
|
||||
// const byManager = stats[manager] ??= {};
|
||||
// const byMethod = byManager[method] ??= {times: [], byArgs: {}};
|
||||
|
||||
// const perf = performance.now();
|
||||
// promise.catch(noop).finally(() => {
|
||||
|
@ -53,8 +53,9 @@ import DEBUG from '../../config/debug';
|
|||
// }
|
||||
|
||||
// setInterval(() => {
|
||||
// // console.log(dT(), '[PROXY] stats', stats, sentCount, sentMethods, sentMethods2);
|
||||
// console.log(dT(), '[PROXY] stats', stats, sentCount, sentMethods, sentMethods2);
|
||||
// sentCount = 0;
|
||||
// stats = {};
|
||||
// sentMethods = {};
|
||||
// sentMethods2 = {};
|
||||
// }, 2000);
|
||||
|
|
|
@ -365,7 +365,7 @@ export class UiNotificationsManager {
|
|||
avatarContext.textBaseline = 'middle';
|
||||
avatarContext.textAlign = 'center';
|
||||
avatarContext.fillStyle = 'white';
|
||||
avatarContext.fillText(abbreviation.text, avatarCanvas.width / 2, avatarCanvas.height * (window.devicePixelRatio > 1 ? .5625 : 5));
|
||||
avatarContext.fillText(abbreviation.text, avatarCanvas.width / 2, avatarCanvas.height * (window.devicePixelRatio > 1 || true ? .5625 : .5));
|
||||
|
||||
notification.image = avatarCanvas.toDataURL();
|
||||
}
|
||||
|
|
|
@ -63,7 +63,14 @@ export default function hasRights(chat: Exclude<Chat, Chat.chatEmpty>, action: C
|
|||
case 'send_media':
|
||||
case 'send_messages':
|
||||
case 'send_polls':
|
||||
case 'send_stickers': {
|
||||
case 'send_stickers':
|
||||
case 'send_photos':
|
||||
case 'send_videos':
|
||||
case 'send_roundvideos':
|
||||
case 'send_audios':
|
||||
case 'send_voices':
|
||||
case 'send_docs':
|
||||
case 'send_plain': {
|
||||
if(!isThread && chat.pFlags.left) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -8,7 +8,12 @@ import type {Document, PhotoSize, VideoSize} from '../../../../layer';
|
|||
import type {DownloadOptions} from '../../../mtproto/apiFileManager';
|
||||
import getDocumentInputFileLocation from './getDocumentInputFileLocation';
|
||||
|
||||
export default function getDocumentDownloadOptions(doc: Document.document, thumb?: PhotoSize.photoSize | VideoSize, queueId?: number, onlyCache?: boolean): DownloadOptions {
|
||||
export default function getDocumentDownloadOptions(
|
||||
doc: Document.document,
|
||||
thumb?: PhotoSize.photoSize | Extract<VideoSize, VideoSize.videoSize>,
|
||||
queueId?: number,
|
||||
onlyCache?: boolean
|
||||
): DownloadOptions {
|
||||
const inputFileLocation = getDocumentInputFileLocation(doc, thumb?.type);
|
||||
|
||||
let mimeType: MTMimeType;
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import {Document, Message, MessageAction, MessageExtendedMedia, MessageMedia, Photo, WebPage} from '../../../../layer';
|
||||
import {Document, Game, Message, MessageAction, MessageExtendedMedia, MessageMedia, Photo, WebPage} from '../../../../layer';
|
||||
|
||||
export default function getMediaFromMessage(message: Message) {
|
||||
export default function getMediaFromMessage(message: Message, onlyInner: true): Photo.photo | Document.document;
|
||||
export default function getMediaFromMessage(message: Message, onlyInner?: false): Photo.photo | Document.document | Game.game | WebPage.webPage;
|
||||
export default function getMediaFromMessage(message: Message, onlyInner = false): Photo.photo | Document.document | Game.game | WebPage.webPage {
|
||||
if(!message) return;
|
||||
|
||||
let media: any;
|
||||
|
@ -15,8 +17,9 @@ export default function getMediaFromMessage(message: Message) {
|
|||
}
|
||||
|
||||
media = (messageMedia as MessageMedia.messageMediaDocument).document ||
|
||||
(messageMedia as MessageMedia.messageMediaPhoto).photo;
|
||||
(messageMedia as MessageMedia.messageMediaPhoto).photo ||
|
||||
(onlyInner ? undefined : (messageMedia as MessageMedia.messageMediaGame).game || messageMedia);
|
||||
}
|
||||
|
||||
return media as Photo.photo | Document.document;
|
||||
return media as any;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import {MyDocument} from '../../appDocsManager';
|
||||
import {VideoSize} from '../../../../layer';
|
||||
|
||||
export default function getStickerEffectThumb(doc: MyDocument) {
|
||||
return doc.video_thumbs?.[0];
|
||||
return doc.video_thumbs?.[0] as Extract<VideoSize, VideoSize.videoSize>;
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ export type DownloadOptions = {
|
|||
|
||||
export type DownloadMediaOptions = {
|
||||
media: Photo.photo | Document.document | WebDocument,
|
||||
thumb?: PhotoSize | VideoSize,
|
||||
thumb?: PhotoSize | Extract<VideoSize, VideoSize.videoSize>,
|
||||
queueId?: number,
|
||||
onlyCache?: boolean,
|
||||
downloadId?: string
|
||||
|
|
|
@ -37,6 +37,7 @@ import pause from '../../helpers/schedulers/pause';
|
|||
import ApiManagerMethods from './api_methods';
|
||||
import {getEnvironment} from '../../environment/utils';
|
||||
import toggleStorages from '../../helpers/toggleStorages';
|
||||
import tsNow from '../../helpers/tsNow';
|
||||
|
||||
/* class RotatableArray<T> {
|
||||
public array: Array<T> = [];
|
||||
|
@ -250,7 +251,7 @@ export class ApiManager extends ApiManagerMethods {
|
|||
|
||||
public async setUserAuth(userAuth: UserAuth | UserId) {
|
||||
if(typeof(userAuth) === 'string' || typeof(userAuth) === 'number') {
|
||||
userAuth = {dcID: 0, date: Date.now() / 1000 | 0, id: userAuth.toPeerId(false)};
|
||||
userAuth = {dcID: 0, date: tsNow(true), id: userAuth.toPeerId(false)};
|
||||
}
|
||||
|
||||
this.rootScope.dispatchEvent('user_auth', userAuth);
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -28,6 +28,7 @@ export type BroadcastEvents = {
|
|||
'chat_full_update': ChatId,
|
||||
'chat_update': ChatId,
|
||||
'chat_toggle_forum': {chatId: ChatId, enabled: boolean},
|
||||
'chat_participant': Update.updateChannelParticipant,
|
||||
|
||||
'channel_update': ChatId,
|
||||
|
||||
|
@ -179,10 +180,7 @@ export class RootScope extends EventListenerBase<BroadcastEventsListeners> {
|
|||
|
||||
this.addEventListener('premium_toggle_private', ({isNew, isPremium}) => {
|
||||
this.premium = isPremium;
|
||||
|
||||
if(!isNew) {
|
||||
this.dispatchEventSingle('premium_toggle', isPremium);
|
||||
}
|
||||
this.dispatchEventSingle('premium_toggle', isPremium);
|
||||
});
|
||||
|
||||
this.addEventListener('connection_status_change', (status) => {
|
||||
|
|
|
@ -163,6 +163,17 @@ const onFirstMount = () => {
|
|||
}).then((code) => {
|
||||
// console.log('got code', code);
|
||||
|
||||
if(code._ === 'auth.sentCodeSuccess') {
|
||||
const {authorization} = code;
|
||||
if(authorization._ === 'auth.authorization') {
|
||||
rootScope.managers.apiManager.setUser(authorization.user);
|
||||
|
||||
import('./pageIm').then((m) => {
|
||||
m.default.mount();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
import('./pageAuthCode').then((m) => m.default.mount(Object.assign(code, {phone_number: phone_number})));
|
||||
}).catch((err) => {
|
||||
toggle();
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -382,7 +382,7 @@
|
|||
height: 100%;
|
||||
|
||||
&-background {
|
||||
rect {
|
||||
.audio-waveform-bar {
|
||||
opacity: .3;
|
||||
|
||||
@include hover() {
|
||||
|
@ -408,7 +408,7 @@
|
|||
margin-top: 1px;
|
||||
}
|
||||
|
||||
rect {
|
||||
&-bar {
|
||||
fill: var(--primary-color);
|
||||
//overflow: visible!important;
|
||||
}
|
||||
|
@ -599,7 +599,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
&:not(.is-out) .audio-toggle:not(.playing) + .audio-waveform-container .audio-waveform-background rect {
|
||||
&:not(.is-out) .audio-toggle:not(.playing) + .audio-waveform-container .audio-waveform-background .audio-waveform-bar {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2437,6 +2437,7 @@ $bubble-border-radius-big: 12px;
|
|||
}
|
||||
|
||||
.audio-transcribed-text {
|
||||
margin-bottom: .75rem;
|
||||
margin-top: .25rem;
|
||||
|
||||
&.is-error {
|
||||
|
@ -2464,9 +2465,9 @@ $bubble-border-radius-big: 12px;
|
|||
}
|
||||
}
|
||||
|
||||
.document-message + .audio-transcribed-text {
|
||||
margin-bottom: .75rem;
|
||||
}
|
||||
// .document-message + .audio-transcribed-text {
|
||||
// margin-bottom: .75rem;
|
||||
// }
|
||||
}
|
||||
|
||||
@keyframes audio-dots {
|
||||
|
@ -2949,7 +2950,7 @@ $bubble-border-radius-big: 12px;
|
|||
|
||||
.audio {
|
||||
&-waveform {
|
||||
rect {
|
||||
&-bar {
|
||||
fill: var(--message-out-primary-color);
|
||||
|
||||
&.active {
|
||||
|
@ -2974,7 +2975,7 @@ $bubble-border-radius-big: 12px;
|
|||
}
|
||||
|
||||
&.is-unread {
|
||||
rect {
|
||||
.audio-waveform-bar {
|
||||
fill: var(--message-out-primary-color);
|
||||
}
|
||||
|
||||
|
|
|
@ -61,7 +61,9 @@
|
|||
border-radius: inherit;
|
||||
}
|
||||
|
||||
.media-photo, .media-video, .media-poster {
|
||||
.media-photo,
|
||||
.media-video,
|
||||
.media-poster {
|
||||
object-fit: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
@ -114,4 +116,23 @@
|
|||
white-space: pre-wrap;
|
||||
}
|
||||
}
|
||||
|
||||
&-cant-send {
|
||||
display: none;
|
||||
margin: 0 auto;
|
||||
color: var(--secondary-text-color);
|
||||
}
|
||||
|
||||
&.cant-send {
|
||||
width: 100% !important;
|
||||
height: 3rem;
|
||||
|
||||
.scrollable-y {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.inline-helper-cant-send {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,6 +107,8 @@
|
|||
pointer-events: none;
|
||||
line-height: var(--line-height);
|
||||
|
||||
@include text-overflow(false);
|
||||
|
||||
@include animation-level(0) {
|
||||
transition: none;
|
||||
}
|
||||
|
@ -385,7 +387,17 @@
|
|||
position: absolute;
|
||||
|
||||
@include animation-level(2) {
|
||||
transition: border-color .1s, transform .1s cubic-bezier(.22, .75, .7, 1.44);
|
||||
transition: border-color .1s, transform .1s cubic-bezier(.22, .75, .7, 1.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-restriction {
|
||||
.checkbox-toggle {
|
||||
background-color: var(--danger-color);
|
||||
|
||||
&:before {
|
||||
border-color: var(--danger-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -91,6 +91,14 @@ $row-border-radius: $border-radius-medium;
|
|||
color: var(--secondary-text-color);
|
||||
}
|
||||
}
|
||||
|
||||
&-row {
|
||||
&.with-delimiter {
|
||||
.row-title:first-child {
|
||||
border-right: 1px solid var(--border-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&-title-right,
|
||||
|
@ -321,4 +329,10 @@ $row-border-radius: $border-radius-medium;
|
|||
color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
|
||||
&.accordion-toggler {
|
||||
.checkbox-field-toggle {
|
||||
pointer-events: all !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -274,6 +274,18 @@
|
|||
}
|
||||
|
||||
.popup-create-contact {
|
||||
.popup-container {
|
||||
padding: 0 1rem 1rem !important;
|
||||
}
|
||||
|
||||
.popup-header {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
margin-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.name-fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
|
@ -50,9 +50,10 @@
|
|||
.checkbox-field {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 3rem;
|
||||
min-height: 3rem;
|
||||
height: auto;
|
||||
margin: 0;
|
||||
padding: 0 1.125rem;
|
||||
padding: .25rem 1.125rem;
|
||||
|
||||
.checkbox-box {
|
||||
left: auto;
|
||||
|
|
|
@ -2102,3 +2102,34 @@ hr {
|
|||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.accordion {
|
||||
overflow: hidden;
|
||||
height: 0;
|
||||
|
||||
&.is-expanded {
|
||||
height: var(--max-height);
|
||||
}
|
||||
|
||||
@include animation-level(2) {
|
||||
transition: height var(--transition-standard-in);
|
||||
}
|
||||
|
||||
&-icon {
|
||||
transform: rotate(0deg) translateY(4px);
|
||||
color: var(--secondary-text-color);
|
||||
font-size: 1.25rem;
|
||||
display: inline-block;
|
||||
line-height: 0;
|
||||
|
||||
@include animation-level(2) {
|
||||
transition: transform var(--transition-standard-in);
|
||||
}
|
||||
}
|
||||
|
||||
&-toggler-expanded {
|
||||
.accordion-icon {
|
||||
transform: translateY(4px) rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue