Browse Source

Voice messages privacy

master
Eduard Kuzmenko 2 years ago committed by r4sas
parent
commit
34572dbf3a
  1. 20
      src/components/chat/bubbles.ts
  2. 33
      src/components/chat/input.ts
  3. 1
      src/components/chat/selection.ts
  4. 2
      src/components/privacySection.ts
  5. 6
      src/components/sendingStatus.ts
  6. 28
      src/components/sidebarLeft/tabs/privacy/voices.ts
  7. 17
      src/components/sidebarLeft/tabs/privacyAndSecurity.ts
  8. 2
      src/config/app.ts
  9. 3
      src/global.d.ts
  10. 8
      src/helpers/getStrippedThumbIfNeeded.ts
  11. 5
      src/helpers/object/copy.ts
  12. 5
      src/lang.ts
  13. 10
      src/layer.d.ts
  14. 100
      src/lib/appManagers/appMessagesManager.ts
  15. 7
      src/lib/mtproto/networker.ts
  16. 1
      src/lib/rootScope.ts
  17. 10
      src/scripts/in/schema_additional_params.json
  18. 8
      src/scss/partials/_chatBubble.scss
  19. 3
      src/scss/partials/_chatlist.scss
  20. 6
      src/scss/style.scss

20
src/components/chat/bubbles.ts

@ -565,6 +565,19 @@ export default class ChatBubbles { @@ -565,6 +565,19 @@ export default class ChatBubbles {
this.safeRenderMessage(message, true, bubble);
});
this.listenerSetter.add(rootScope)('message_error', async({storageKey, tempId}) => {
if(storageKey !== this.chat.messagesStorageKey) return;
const bubble = this.bubbles[tempId];
if(!bubble) return;
await getHeavyAnimationPromise();
if(this.bubbles[tempId] !== bubble) return;
bubble.classList.remove('is-outgoing');
bubble.classList.add('is-error');
});
this.listenerSetter.add(rootScope)('album_edit', ({peerId, messages, deletedMids}) => {
if(peerId !== this.peerId) return;
@ -2094,7 +2107,7 @@ export default class ChatBubbles { @@ -2094,7 +2107,7 @@ export default class ChatBubbles {
if(bubble) {
this.unreadOut.delete(msgId);
if(bubble.classList.contains('is-outgoing')) {
if(bubble.classList.contains('is-outgoing') || bubble.classList.contains('is-error')) {
continue;
}
@ -3764,12 +3777,13 @@ export default class ChatBubbles { @@ -3764,12 +3777,13 @@ export default class ChatBubbles {
if(our) {
if(message.pFlags.unread || isOutgoing) this.unreadOut.add(message.mid);
let status = '';
if(isOutgoing) status = 'is-sending';
if(message.error) status = 'is-error';
else if(isOutgoing) status = 'is-sending';
else status = message.pFlags.unread || (message as Message.message).pFlags.is_scheduled ? 'is-sent' : 'is-read';
bubble.classList.add(status);
}
if(isOutgoing) {
if(isOutgoing && !message.error) {
bubble.classList.add('is-outgoing');
}

33
src/components/chat/input.ts

@ -17,10 +17,9 @@ import emoticonsDropdown from '../emoticonsDropdown'; @@ -17,10 +17,9 @@ import emoticonsDropdown from '../emoticonsDropdown';
import PopupCreatePoll from '../popups/createPoll';
import PopupForward from '../popups/forward';
import PopupNewMedia from '../popups/newMedia';
import {toast} from '../toast';
import {toast, toastNew} from '../toast';
import {wrapReply} from '../wrappers';
import InputField from '../inputField';
import {MessageEntity, DraftMessage, WebPage, Message, ChatFull, UserFull} from '../../layer';
import {MessageEntity, DraftMessage, WebPage, Message, UserFull} from '../../layer';
import StickersHelper from './stickersHelper';
import ButtonIcon from '../buttonIcon';
import ButtonMenuToggle from '../buttonMenuToggle';
@ -96,6 +95,7 @@ import filterAsync from '../../helpers/array/filterAsync'; @@ -96,6 +95,7 @@ import filterAsync from '../../helpers/array/filterAsync';
import InputFieldAnimated from '../inputFieldAnimated';
import getStickerEffectThumb from '../../lib/appManagers/utils/stickers/getStickerEffectThumb';
import PopupStickers from '../popups/stickers';
import wrapPeerTitle from '../wrappers/peerTitle';
const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@ -2086,7 +2086,8 @@ export default class ChatInput { @@ -2086,7 +2086,8 @@ export default class ChatInput {
this.sendMessage();
}
} else {
if(this.chat.peerId.isAnyChat() && !(await this.chat.canSend('send_media'))) {
const isAnyChat = this.chat.peerId.isAnyChat();
if(isAnyChat && !(await this.chat.canSend('send_media'))) {
toast(POSTING_MEDIA_NOT_ALLOWED);
return;
}
@ -2094,6 +2095,23 @@ export default class ChatInput { @@ -2094,6 +2095,23 @@ export default class ChatInput {
this.chatInput.classList.add('is-locked');
blurActiveElement();
let restricted = false;
if(!isAnyChat) {
const userFull = await this.managers.appProfileManager.getProfile(this.chat.peerId.toUserId());
if(userFull?.pFlags.voice_messages_forbidden) {
toastNew({
langPackKey: 'Chat.SendVoice.PrivacyError',
langPackArguments: [await wrapPeerTitle({peerId: this.chat.peerId})]
});
restricted = true;
}
}
if(restricted) {
this.chatInput.classList.remove('is-locked');
return;
}
this.recorder.start().then(() => {
this.releaseMediaPlayback = appMediaPlaybackController.setSingleMedia();
this.recordCanceled = false;
@ -2427,6 +2445,13 @@ export default class ChatInput { @@ -2427,6 +2445,13 @@ export default class ChatInput {
...sendingParams,
dropAuthor: this.forwardElements && this.forwardElements.hideSender.checkboxField.checked,
dropCaptions: this.isDroppingCaptions()
}).catch(async(err: ApiError) => {
if(err.type === 'VOICE_MESSAGES_FORBIDDEN') {
toastNew({
langPackKey: 'Chat.SendVoice.PrivacyError',
langPackArguments: [await wrapPeerTitle({peerId})]
});
}
});
}

1
src/components/chat/selection.ts

@ -853,6 +853,7 @@ export default class ChatSelection extends AppSelection { @@ -853,6 +853,7 @@ export default class ChatSelection extends AppSelection {
public canSelectBubble(bubble: HTMLElement) {
return !bubble.classList.contains('service') &&
!bubble.classList.contains('is-outgoing') &&
!bubble.classList.contains('is-error') &&
!bubble.classList.contains('bubble-first') &&
!bubble.classList.contains('avoid-selection');
}

2
src/components/privacySection.ts

@ -192,7 +192,7 @@ export default class PrivacySection { @@ -192,7 +192,7 @@ export default class PrivacySection {
]>);
for(const [k, chatKey, usersKey] of a) {
if(this.exceptions.get(k).row.container.classList.contains('hide')) {
return;
continue;
}
const _peerIds = this.peerIds[k];

6
src/components/sendingStatus.ts

@ -30,9 +30,11 @@ export function setSendingStatus( @@ -30,9 +30,11 @@ export function setSendingStatus(
message?: Message.message | Message.messageService,
disableAnimationIfRippleFound?: boolean
) {
let className: 'check' | 'checks' | 'sending';
let className: 'check' | 'checks' | 'sending' | 'sendingerror';
if(message?.pFlags.out) {
if(message.pFlags.is_outgoing) {
if(message.error) {
className = 'sendingerror';
} else if(message.pFlags.is_outgoing) {
className = 'sending';
} else if(message.pFlags.unread) {
className = 'check';

28
src/components/sidebarLeft/tabs/privacy/voices.ts

@ -0,0 +1,28 @@ @@ -0,0 +1,28 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import {SliderSuperTabEventable} from '../../../sliderTab';
import PrivacySection from '../../../privacySection';
import {LangPackKey} from '../../../../lib/langPack';
export default class AppPrivacyVoicesTab extends SliderSuperTabEventable {
protected init() {
this.header.classList.add('with-border');
this.container.classList.add('privacy-tab', 'privacy-voices');
this.setTitle('PrivacyVoiceMessages');
const caption: LangPackKey = 'PrivacyVoiceMessagesInfo';
new PrivacySection({
tab: this,
title: 'PrivacyVoiceMessagesTitle',
inputKey: 'inputPrivacyKeyVoiceMessages',
captions: [caption, caption, caption],
exceptionTexts: ['PrivacySettingsController.NeverAllow', 'PrivacySettingsController.AlwaysAllow'],
appendTo: this.scrollable,
managers: this.managers
});
}
}

17
src/components/sidebarLeft/tabs/privacyAndSecurity.ts

@ -32,6 +32,7 @@ import PrivacyType from '../../../lib/appManagers/utils/privacy/privacyType'; @@ -32,6 +32,7 @@ import PrivacyType from '../../../lib/appManagers/utils/privacy/privacyType';
import confirmationPopup, {PopupConfirmationOptions} from '../../confirmationPopup';
import noop from '../../../helpers/noop';
import {toastNew} from '../../toast';
import AppPrivacyVoicesTab from './privacy/voices';
export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
private activeSessionsRow: Row;
@ -209,6 +210,19 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable { @@ -209,6 +210,19 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
listenerSetter: this.listenerSetter
});
const voicesRow = rowsByKeys['inputPrivacyKeyVoiceMessages'] = new Row({
titleLangKey: 'PrivacyVoiceMessagesTitle',
subtitleLangKey: SUBTITLE,
clickable: () => {
if(!rootScope.premium) {
toastNew({langPackKey: 'PrivacyVoiceMessagesPremiumOnly'});
} else {
this.slider.createTab(AppPrivacyVoicesTab).open();
}
},
listenerSetter: this.listenerSetter
});
const updatePrivacyRow = (key: InputPrivacyKey['_']) => {
const row = rowsByKeys[key];
if(!row) {
@ -236,7 +250,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable { @@ -236,7 +250,8 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
photoVisibilityRow.container,
callRow.container,
linkAccountRow.container,
groupChatsAddRow.container
groupChatsAddRow.container,
voicesRow.container
);
this.scrollable.append(section.container);

2
src/config/app.ts

@ -19,7 +19,7 @@ const App = { @@ -19,7 +19,7 @@ const App = {
version: process.env.VERSION,
versionFull: process.env.VERSION_FULL,
build: +process.env.BUILD,
langPackVersion: '0.4.5',
langPackVersion: '0.4.6',
langPack: 'macos',
langPackCode: 'en',
domains: [MAIN_DOMAIN] as string[],

3
src/global.d.ts vendored

@ -42,7 +42,8 @@ declare global { @@ -42,7 +42,8 @@ declare global {
type ServerErrorType = 'FILE_REFERENCE_EXPIRED' | 'SESSION_REVOKED' | 'AUTH_KEY_DUPLICATED' |
'SESSION_PASSWORD_NEEDED' | 'CONNECTION_NOT_INITED' | 'ERROR_EMPTY' | 'MTPROTO_CLUSTER_INVALID' |
'BOT_PRECHECKOUT_TIMEOUT' | 'TMP_PASSWORD_INVALID' | 'PASSWORD_HASH_INVALID' | 'CHANNEL_PRIVATE';
'BOT_PRECHECKOUT_TIMEOUT' | 'TMP_PASSWORD_INVALID' | 'PASSWORD_HASH_INVALID' | 'CHANNEL_PRIVATE' |
'VOICE_MESSAGES_FORBIDDEN' | 'PHOTO_INVALID_DIMENSIONS' | 'PHOTO_SAVE_FILE_INVALID';
type ErrorType = LocalErrorType | ServerErrorType;

8
src/helpers/getStrippedThumbIfNeeded.ts

@ -6,13 +6,19 @@ @@ -6,13 +6,19 @@
import type {MyDocument} from '../lib/appManagers/appDocsManager';
import type {MyPhoto} from '../lib/appManagers/appPhotosManager';
import {THUMB_TYPE_FULL} from '../lib/mtproto/mtproto_config';
import type {ThumbCache} from '../lib/storages/thumbs';
import getImageFromStrippedThumb from './getImageFromStrippedThumb';
export default function getStrippedThumbIfNeeded(photo: MyPhoto | MyDocument, cacheContext: ThumbCache, useBlur: boolean, ignoreCache = false) {
const isVideo = (['video', 'gif'] as MyDocument['type'][]).includes((photo as MyDocument).type);
if(!cacheContext.downloaded || isVideo || ignoreCache) {
if(photo._ === 'document' && cacheContext.downloaded && !ignoreCache && !isVideo) {
if(
photo._ === 'document' &&
cacheContext.downloaded &&
!ignoreCache &&
(!isVideo || cacheContext.type !== THUMB_TYPE_FULL)
) {
return null;
}

5
src/helpers/object/copy.ts

@ -16,6 +16,11 @@ export default function copy<T>(obj: T): T { @@ -16,6 +16,11 @@ export default function copy<T>(obj: T): T {
return clonedArr;
}
if(ArrayBuffer.isView(obj)) {
// @ts-ignore
return obj.slice();
}
// lastly, handle objects
// @ts-ignore
const clonedObj = new obj.constructor();

5
src/lang.ts

@ -759,6 +759,10 @@ const lang = { @@ -759,6 +759,10 @@ const lang = {
'NewChatsFromNonContacts': 'New chats from unknown users',
'ArchiveAndMute': 'Archive and Mute',
'ArchiveAndMuteInfo': 'Automatically archive and mute new chats, groups and channels from non-contacts.',
'PrivacyVoiceMessages': 'Voice Messages',
'PrivacyVoiceMessagesTitle': 'Who can send me voice or video messages?',
'PrivacyVoiceMessagesInfo': 'You can restrict who can send you voice or video messages with granular precision.',
'PrivacyVoiceMessagesPremiumOnly': 'Only subscribers of *Telegram Premium* can restrict receiving voice messages.',
// * macos
'AccountSettings.Filters': 'Chat Folders',
@ -835,6 +839,7 @@ const lang = { @@ -835,6 +839,7 @@ const lang = {
'Chat.DropQuickDesc': 'in a quick way',
'Chat.DropAsFilesDesc': 'without compression',
'Chat.Edit.Cancel.Text': 'Are you sure you want to discard all changes?',
'Chat.SendVoice.PrivacyError': '%@ doesn\'t accept voice and video messages',
'Chat.Service.Call.Cancelled': 'Cancelled',
'Chat.Service.Call.Missed': 'Missed',
'Chat.Service.PeerJoinedTelegram': '%@ joined Telegram',

10
src/layer.d.ts vendored

@ -881,14 +881,15 @@ export namespace Message { @@ -881,14 +881,15 @@ export namespace Message {
viaBotId?: PeerId,
clear_history?: boolean,
pending?: boolean,
error?: any,
error?: ApiError,
send?: () => Promise<any>,
totalEntities?: MessageEntity[],
reply_to_mid?: number,
savedFrom?: string,
sponsoredMessage?: SponsoredMessage.sponsoredMessage,
promise?: CancellablePromise<void>,
uploadingFileName?: string
uploadingFileName?: string,
storageKey?: MessagesStorageKey
};
export type messageService = {
@ -920,11 +921,12 @@ export namespace Message { @@ -920,11 +921,12 @@ export namespace Message {
rReply?: string,
viaBotId?: PeerId,
pending?: boolean,
error?: any,
error?: ApiError,
send?: () => Promise<any>,
random_id?: string,
reply_to_mid?: number,
clear_history?: boolean
clear_history?: boolean,
storageKey?: MessagesStorageKey
};
}

100
src/lib/appManagers/appMessagesManager.ts

@ -530,17 +530,13 @@ export class AppMessagesManager extends AppManager { @@ -530,17 +530,13 @@ export class AppMessagesManager extends AppManager {
};
}
const toggleError = (on: boolean) => {
if(on) {
message.error = true;
} else {
delete message.error;
}
const toggleError = (error?: ApiError) => {
this.onMessagesSendError([message], error);
this.rootScope.dispatchEvent('messages_pending');
};
message.send = () => {
toggleError(false);
toggleError();
const sentRequestOptions: PendingAfterMsg = {};
if(this.pendingAfterMsgs[peerId]) {
sentRequestOptions.afterMessageId = this.pendingAfterMsgs[peerId].messageId;
@ -637,9 +633,10 @@ export class AppMessagesManager extends AppManager { @@ -637,9 +633,10 @@ export class AppMessagesManager extends AppManager {
// ApiUpdatesManager.processUpdateMessage(upd)
// }, 5000)
message.promise.resolve();
}, (error: any) => {
toggleError(true);
}, (error: ApiError) => {
toggleError(error);
message.promise.reject(error);
throw error;
}).finally(() => {
if(this.pendingAfterMsgs[peerId] === sentRequestOptions) {
delete this.pendingAfterMsgs[peerId];
@ -916,13 +913,8 @@ export class AppMessagesManager extends AppManager { @@ -916,13 +913,8 @@ export class AppMessagesManager extends AppManager {
this.uploadFilePromises[uploadingFileName] = sentDeferred;
}
const toggleError = (on: boolean) => {
if(on) {
message.error = true;
} else {
delete message.error;
}
const toggleError = (error?: ApiError) => {
this.onMessagesSendError([message], error);
this.rootScope.dispatchEvent('messages_pending');
};
@ -1012,8 +1004,9 @@ export class AppMessagesManager extends AppManager { @@ -1012,8 +1004,9 @@ export class AppMessagesManager extends AppManager {
}
sentDeferred.resolve(inputMedia);
}, (/* error */) => {
toggleError(true);
}, (error: ApiError) => {
toggleError(error);
throw error;
});
return sentDeferred;
@ -1057,7 +1050,7 @@ export class AppMessagesManager extends AppManager { @@ -1057,7 +1050,7 @@ export class AppMessagesManager extends AppManager {
send_as: options.sendAsPeerId ? this.appPeersManager.getInputPeerById(options.sendAsPeerId) : undefined
}).then((updates) => {
this.apiUpdatesManager.processUpdateMessage(updates);
}, (error) => {
}, (error: ApiError) => {
if(attachType === 'photo' &&
error.code === 400 &&
(error.type === 'PHOTO_INVALID_DIMENSIONS' ||
@ -1068,7 +1061,7 @@ export class AppMessagesManager extends AppManager { @@ -1068,7 +1061,7 @@ export class AppMessagesManager extends AppManager {
return;
}
toggleError(true);
toggleError(error);
throw error;
});
});
@ -1173,13 +1166,8 @@ export class AppMessagesManager extends AppManager { @@ -1173,13 +1166,8 @@ export class AppMessagesManager extends AppManager {
// * test pending
// return;
const toggleError = (message: any, on: boolean) => {
if(on) {
message.error = true;
} else {
delete message.error;
}
const toggleError = (message: Message.message, error?: ApiError) => {
this.onMessagesSendError([message], error);
this.rootScope.dispatchEvent('messages_pending');
};
@ -1201,8 +1189,8 @@ export class AppMessagesManager extends AppManager { @@ -1201,8 +1189,8 @@ export class AppMessagesManager extends AppManager {
}).then((updates) => {
this.apiUpdatesManager.processUpdateMessage(updates);
deferred.resolve();
}, (error) => {
messages.forEach((message) => toggleError(message, true));
}, (error: ApiError) => {
messages.forEach((message) => toggleError(message, error));
deferred.reject(error);
});
}
@ -1243,13 +1231,9 @@ export class AppMessagesManager extends AppManager { @@ -1243,13 +1231,9 @@ export class AppMessagesManager extends AppManager {
}
return inputSingleMedia;
}).catch((err: any) => {
if(err.name === 'AbortError') {
return null;
}
}).catch((err: ApiError) => {
this.log.error('sendAlbum upload item error:', err, message);
toggleError(message, true);
toggleError(message, err);
throw err;
});
});
@ -1366,19 +1350,8 @@ export class AppMessagesManager extends AppManager { @@ -1366,19 +1350,8 @@ export class AppMessagesManager extends AppManager {
message.media = media;
const toggleError = (on: boolean) => {
/* const historyMessage = this.messagesForHistory[messageId];
if (on) {
message.error = true
if (historyMessage) {
historyMessage.error = true
}
} else {
delete message.error
if (historyMessage) {
delete historyMessage.error
}
} */
const toggleError = (error?: ApiError) => {
this.onMessagesSendError([message], error);
this.rootScope.dispatchEvent('messages_pending');
};
@ -1428,8 +1401,9 @@ export class AppMessagesManager extends AppManager { @@ -1428,8 +1401,9 @@ export class AppMessagesManager extends AppManager {
}
this.apiUpdatesManager.processUpdateMessage(updates);
}, (error) => {
toggleError(true);
}, (error: ApiError) => {
toggleError(error);
throw error;
}).finally(() => {
if(this.pendingAfterMsgs[peerId] === sentRequestOptions) {
delete this.pendingAfterMsgs[peerId];
@ -1469,6 +1443,7 @@ export class AppMessagesManager extends AppManager { @@ -1469,6 +1443,7 @@ export class AppMessagesManager extends AppManager {
const messageId = message.id;
const peerId = this.getMessagePeer(message);
const storage = options.isScheduled ? this.getScheduledMessagesStorage(peerId) : this.getHistoryMessagesStorage(peerId);
message.storageKey = storage.key;
const callbacks: Array<() => void> = [];
if(options.isScheduled) {
// if(!options.isGroupedItem) {
@ -2146,6 +2121,9 @@ export class AppMessagesManager extends AppManager { @@ -2146,6 +2121,9 @@ export class AppMessagesManager extends AppManager {
}, sentRequestOptions).then((updates) => {
this.log('forwardMessages updates:', updates);
this.apiUpdatesManager.processUpdateMessage(updates);
}, (error: ApiError) => {
this.onMessagesSendError(newMessages, error);
throw error;
}).finally(() => {
if(this.pendingAfterMsgs[peerId] === sentRequestOptions) {
delete this.pendingAfterMsgs[peerId];
@ -2173,6 +2151,26 @@ export class AppMessagesManager extends AppManager { @@ -2173,6 +2151,26 @@ export class AppMessagesManager extends AppManager {
// };
}
private onMessagesSendError(messages: Message.message[], error?: ApiError) {
messages.forEach((message) => {
if(message.error === error) {
return;
}
if(error) {
message.error = error;
this.rootScope.dispatchEvent('message_error', {storageKey: message.storageKey, tempId: message.mid, error});
const dialog = this.getDialogOnly(message.peerId);
if(dialog) {
this.rootScope.dispatchEvent('dialog_unread', {peerId: message.peerId, dialog});
}
} else {
delete message.error;
}
});
}
public getMessagesStorageByKey(key: MessagesStorageKey) {
const s = key.split('_');
const peerId: PeerId = +s[0];
@ -3208,7 +3206,7 @@ export class AppMessagesManager extends AppManager { @@ -3208,7 +3206,7 @@ export class AppMessagesManager extends AppManager {
message.pFlags.out ||
this.appChatsManager.getChat(message.peerId.toChatId())._ === 'chat' ||
this.appChatsManager.hasRights(message.peerId.toChatId(), 'delete_messages')
) && !message.pFlags.is_outgoing;
) && (!message.pFlags.is_outgoing || !!message.error);
}
public getReplyKeyboard(peerId: PeerId) {

7
src/lib/mtproto/networker.ts

@ -1892,7 +1892,12 @@ export default class MTPNetworker { @@ -1892,7 +1892,12 @@ export default class MTPNetworker {
} */
const pingId = message.ping_id;
if(this.lastPingDelayDisconnectId === pingId) {
this.pingDelayDisconnectDeferred.resolve('pong');
const deferred = this.pingDelayDisconnectDeferred;
if(deferred) {
deferred.resolve('pong');
} else {
this.log('ping deferred deleted', pingId);
}
}
break;

1
src/lib/rootScope.ts

@ -71,6 +71,7 @@ export type BroadcastEvents = { @@ -71,6 +71,7 @@ export type BroadcastEvents = {
'message_edit': {storageKey: MessagesStorageKey, peerId: PeerId, mid: number, message: MyMessage},
'message_sent': {storageKey: MessagesStorageKey, tempId: number, tempMessage: any, mid: number, message: MyMessage},
'message_error': {storageKey: MessagesStorageKey, tempId: number, error: ApiError},
'messages_views': {peerId: PeerId, mid: number, views: number}[],
'messages_reactions': {message: Message.message, changedResults: ReactionCount[]}[],
'messages_pending': void,

10
src/scripts/in/schema_additional_params.json

@ -90,7 +90,7 @@ @@ -90,7 +90,7 @@
{"name": "viaBotId", "type": "PeerId"},
{"name": "clear_history", "type": "boolean"},
{"name": "pending", "type": "boolean"},
{"name": "error", "type": "any"},
{"name": "error", "type": "ApiError"},
{"name": "send", "type": "() => Promise<any>"},
{"name": "totalEntities", "type": "MessageEntity[]"},
{"name": "reply_to_mid", "type": "number"},
@ -99,7 +99,8 @@ @@ -99,7 +99,8 @@
{"name": "local", "type": "true"},
{"name": "sponsoredMessage", "type": "SponsoredMessage.sponsoredMessage"},
{"name": "promise", "type": "CancellablePromise<void>"},
{"name": "uploadingFileName", "type": "string"}
{"name": "uploadingFileName", "type": "string"},
{"name": "storageKey", "type": "MessagesStorageKey"}
]
}, {
"predicate": "messageService",
@ -114,12 +115,13 @@ @@ -114,12 +115,13 @@
{"name": "viaBotId", "type": "PeerId"},
{"name": "is_single", "type": "true"},
{"name": "pending", "type": "boolean"},
{"name": "error", "type": "any"},
{"name": "error", "type": "ApiError"},
{"name": "send", "type": "() => Promise<any>"},
{"name": "random_id", "type": "string"},
{"name": "reply_to_mid", "type": "number"},
{"name": "clear_history", "type": "boolean"},
{"name": "local", "type": "true"}
{"name": "local", "type": "true"},
{"name": "storageKey", "type": "MessagesStorageKey"}
]
}, {
"predicate": "messageEmpty",

8
src/scss/partials/_chatBubble.scss

@ -2566,6 +2566,14 @@ $bubble-beside-button-width: 38px; @@ -2566,6 +2566,14 @@ $bubble-beside-button-width: 38px;
}
}
&.is-error {
.time:after,
.time .inner:after {
content: $tgico-sendingerror;
color: var(--message-error-color);
}
}
/* &.is-reply .name {
display: none;
} */

3
src/scss/partials/_chatlist.scss

@ -268,7 +268,8 @@ ul.chatlist { @@ -268,7 +268,8 @@ ul.chatlist {
.message-status,
.text-highlight,
.premium-icon,
.verified-icon {
.verified-icon,
.sending-status-icon {
color: #fff;
}

6
src/scss/style.scss

@ -228,6 +228,7 @@ $chat-input-inner-padding-handhelds: .25rem; @@ -228,6 +228,7 @@ $chat-input-inner-padding-handhelds: .25rem;
--message-primary-color: var(--primary-color);
--light-filled-message-primary-color: var(--light-filled-primary-color);
--message-secondary-color: var(--secondary-color);
--message-error-color: var(--danger-color);
$message-out-background-color: #eeffde;
@include splitColor(message-out-background-color, $message-out-background-color, true, true);
@ -300,6 +301,7 @@ $chat-input-inner-padding-handhelds: .25rem; @@ -300,6 +301,7 @@ $chat-input-inner-padding-handhelds: .25rem;
--message-checkbox-color: var(--primary-color);
--message-checkbox-border-color: #fff;
--message-secondary-color: var(--secondary-color);
--message-error-color: #fff;
$message-out-background-color: #8774E1;
//@include splitColor(message-out-background-color, #ae582d, true, true);
@ -1637,6 +1639,10 @@ hr { @@ -1637,6 +1639,10 @@ hr {
}
} */
.tgico-sendingerror {
color: var(--danger-color);
}
&-icon {
position: absolute;
line-height: 1 !important;

Loading…
Cancel
Save