Ability to edit message when input is locked
Fix unneeded focus on hidden input Throttle emoji interactive animation Remove link to Webogram
This commit is contained in:
parent
18737324e6
commit
988ed5f306
|
@ -252,6 +252,7 @@ export default class ChatInput {
|
||||||
public sendAsPeerId: PeerId;
|
public sendAsPeerId: PeerId;
|
||||||
|
|
||||||
private replyInTopicOverlay: HTMLDivElement;
|
private replyInTopicOverlay: HTMLDivElement;
|
||||||
|
private restoreInputLock: () => void;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private chat: Chat,
|
private chat: Chat,
|
||||||
|
@ -368,12 +369,18 @@ export default class ChatInput {
|
||||||
this.inputContainer.append(c);
|
this.inputContainer.append(c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private createButtonIcon(...args: Parameters<typeof ButtonIcon>) {
|
||||||
|
const button = ButtonIcon(...args);
|
||||||
|
button.tabIndex = -1;
|
||||||
|
return button;
|
||||||
|
}
|
||||||
|
|
||||||
public constructPeerHelpers() {
|
public constructPeerHelpers() {
|
||||||
this.replyElements.container = document.createElement('div');
|
this.replyElements.container = document.createElement('div');
|
||||||
this.replyElements.container.classList.add('reply-wrapper');
|
this.replyElements.container.classList.add('reply-wrapper');
|
||||||
|
|
||||||
this.replyElements.iconBtn = ButtonIcon('');
|
this.replyElements.iconBtn = this.createButtonIcon('');
|
||||||
this.replyElements.cancelBtn = ButtonIcon('close reply-cancel', {noRipple: true});
|
this.replyElements.cancelBtn = this.createButtonIcon('close reply-cancel', {noRipple: true});
|
||||||
|
|
||||||
this.replyElements.container.append(this.replyElements.iconBtn, this.replyElements.cancelBtn);
|
this.replyElements.container.append(this.replyElements.iconBtn, this.replyElements.cancelBtn);
|
||||||
|
|
||||||
|
@ -501,7 +508,7 @@ export default class ChatInput {
|
||||||
this.replyInTopicOverlay.classList.add('reply-in-topic-overlay', 'hide');
|
this.replyInTopicOverlay.classList.add('reply-in-topic-overlay', 'hide');
|
||||||
this.replyInTopicOverlay.append(i18n('Chat.Input.ReplyToAnswer'));
|
this.replyInTopicOverlay.append(i18n('Chat.Input.ReplyToAnswer'));
|
||||||
|
|
||||||
this.btnToggleEmoticons = ButtonIcon('none toggle-emoticons', {noRipple: true});
|
this.btnToggleEmoticons = this.createButtonIcon('none toggle-emoticons', {noRipple: true});
|
||||||
|
|
||||||
this.inputMessageContainer = document.createElement('div');
|
this.inputMessageContainer = document.createElement('div');
|
||||||
this.inputMessageContainer.classList.add('input-message-container');
|
this.inputMessageContainer.classList.add('input-message-container');
|
||||||
|
@ -530,7 +537,7 @@ export default class ChatInput {
|
||||||
});
|
});
|
||||||
}, {listenerSetter: this.listenerSetter});
|
}, {listenerSetter: this.listenerSetter});
|
||||||
|
|
||||||
this.btnScheduled = ButtonIcon('scheduled btn-scheduled float hide', {noRipple: true});
|
this.btnScheduled = this.createButtonIcon('scheduled btn-scheduled float hide', {noRipple: true});
|
||||||
|
|
||||||
attachClickEvent(this.btnScheduled, (e) => {
|
attachClickEvent(this.btnScheduled, (e) => {
|
||||||
this.appImManager.openScheduled(this.chat.peerId);
|
this.appImManager.openScheduled(this.chat.peerId);
|
||||||
|
@ -554,7 +561,7 @@ export default class ChatInput {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
this.btnToggleReplyMarkup = ButtonIcon('botcom toggle-reply-markup float hide', {noRipple: true});
|
this.btnToggleReplyMarkup = this.createButtonIcon('botcom toggle-reply-markup float hide', {noRipple: true});
|
||||||
this.replyKeyboard = new ReplyKeyboard({
|
this.replyKeyboard = new ReplyKeyboard({
|
||||||
appendTo: this.rowsWrapper,
|
appendTo: this.rowsWrapper,
|
||||||
listenerSetter: this.listenerSetter,
|
listenerSetter: this.listenerSetter,
|
||||||
|
@ -780,7 +787,7 @@ export default class ChatInput {
|
||||||
this.inlineHelper = new InlineHelper(this.rowsWrapper, this.autocompleteHelperController, this.chat, this.managers);
|
this.inlineHelper = new InlineHelper(this.rowsWrapper, this.autocompleteHelperController, this.chat, this.managers);
|
||||||
this.rowsWrapper.append(this.newMessageWrapper);
|
this.rowsWrapper.append(this.newMessageWrapper);
|
||||||
|
|
||||||
this.btnCancelRecord = ButtonIcon('binfilled btn-circle btn-record-cancel chat-secondary-button');
|
this.btnCancelRecord = this.createButtonIcon('binfilled btn-circle btn-record-cancel chat-secondary-button');
|
||||||
|
|
||||||
this.btnSendContainer = document.createElement('div');
|
this.btnSendContainer = document.createElement('div');
|
||||||
this.btnSendContainer.classList.add('btn-send-container');
|
this.btnSendContainer.classList.add('btn-send-container');
|
||||||
|
@ -788,7 +795,7 @@ export default class ChatInput {
|
||||||
this.recordRippleEl = document.createElement('div');
|
this.recordRippleEl = document.createElement('div');
|
||||||
this.recordRippleEl.classList.add('record-ripple');
|
this.recordRippleEl.classList.add('record-ripple');
|
||||||
|
|
||||||
this.btnSend = ButtonIcon('none btn-circle btn-send animated-button-icon');
|
this.btnSend = this.createButtonIcon('none btn-circle btn-send animated-button-icon');
|
||||||
this.btnSend.insertAdjacentHTML('afterbegin', `
|
this.btnSend.insertAdjacentHTML('afterbegin', `
|
||||||
<span class="tgico tgico-send"></span>
|
<span class="tgico tgico-send"></span>
|
||||||
<span class="tgico tgico-schedule"></span>
|
<span class="tgico tgico-schedule"></span>
|
||||||
|
@ -1591,7 +1598,10 @@ export default class ChatInput {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const oldKey = i.key;
|
||||||
i.compareAndUpdate({key});
|
i.compareAndUpdate({key});
|
||||||
|
|
||||||
|
return {oldKey, newKey: key};
|
||||||
}
|
}
|
||||||
|
|
||||||
private filterAttachMenuButtons() {
|
private filterAttachMenuButtons() {
|
||||||
|
@ -1617,15 +1627,23 @@ export default class ChatInput {
|
||||||
chatInput.classList.remove('no-transition');
|
chatInput.classList.remove('no-transition');
|
||||||
}
|
}
|
||||||
|
|
||||||
this.updateMessageInputPlaceholder(placeholderKey);
|
const isEditingAndLocked = canSend && !canSendPlain && this.restoreInputLock;
|
||||||
|
|
||||||
if(!canSend || !canSendPlain) {
|
!isEditingAndLocked && this.updateMessageInputPlaceholder(placeholderKey);
|
||||||
|
|
||||||
|
if(isEditingAndLocked) {
|
||||||
|
this.restoreInputLock = () => {
|
||||||
|
this.updateMessageInputPlaceholder(placeholderKey);
|
||||||
|
this.messageInput.contentEditable = 'false';
|
||||||
|
};
|
||||||
|
} else if(!canSend || !canSendPlain) {
|
||||||
messageInput.contentEditable = 'false';
|
messageInput.contentEditable = 'false';
|
||||||
|
|
||||||
if(!canSendPlain) {
|
if(!canSendPlain) {
|
||||||
this.messageInputField.onFakeInput(undefined, true);
|
this.messageInputField.onFakeInput(undefined, true);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
this.restoreInputLock = undefined;
|
||||||
messageInput.contentEditable = 'true';
|
messageInput.contentEditable = 'true';
|
||||||
this.setDraft(undefined, false);
|
this.setDraft(undefined, false);
|
||||||
|
|
||||||
|
@ -1650,6 +1668,7 @@ export default class ChatInput {
|
||||||
withLinebreaks: true
|
withLinebreaks: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.messageInputField.input.tabIndex = -1;
|
||||||
this.messageInputField.input.classList.replace('input-field-input', 'input-message-input');
|
this.messageInputField.input.classList.replace('input-field-input', 'input-message-input');
|
||||||
this.messageInputField.inputFake.classList.replace('input-field-input', 'input-message-input');
|
this.messageInputField.inputFake.classList.replace('input-field-input', 'input-message-input');
|
||||||
this.messageInput = this.messageInputField.input;
|
this.messageInput = this.messageInputField.input;
|
||||||
|
@ -2354,7 +2373,7 @@ export default class ChatInput {
|
||||||
foundHelper = this.inlineHelper;
|
foundHelper = this.inlineHelper;
|
||||||
|
|
||||||
if(!this.btnPreloader) {
|
if(!this.btnPreloader) {
|
||||||
this.btnPreloader = ButtonIcon('none btn-preloader float show disable-hover', {noRipple: true});
|
this.btnPreloader = this.createButtonIcon('none btn-preloader float show disable-hover', {noRipple: true});
|
||||||
putPreloader(this.btnPreloader, true);
|
putPreloader(this.btnPreloader, true);
|
||||||
this.inputMessageContainer.parentElement.insertBefore(this.btnPreloader, this.inputMessageContainer.nextSibling);
|
this.inputMessageContainer.parentElement.insertBefore(this.btnPreloader, this.inputMessageContainer.nextSibling);
|
||||||
} else {
|
} else {
|
||||||
|
@ -2901,12 +2920,27 @@ export default class ChatInput {
|
||||||
|
|
||||||
let input = wrapDraftText(message.message, {entities: message.totalEntities, wrappingForPeerId: this.chat.peerId});
|
let input = wrapDraftText(message.message, {entities: message.totalEntities, wrappingForPeerId: this.chat.peerId});
|
||||||
const f = async() => {
|
const f = async() => {
|
||||||
|
let restoreInputLock: () => void;
|
||||||
|
if(!this.messageInput.isContentEditable) {
|
||||||
|
const placeholderKey = await this.getPlaceholderKey(true);
|
||||||
|
const {contentEditable} = this.messageInput;
|
||||||
|
this.messageInput.contentEditable = 'true';
|
||||||
|
const {oldKey} = this.updateMessageInputPlaceholder(placeholderKey);
|
||||||
|
|
||||||
|
restoreInputLock = () => {
|
||||||
|
this.messageInput.contentEditable = contentEditable;
|
||||||
|
this.updateMessageInputPlaceholder(oldKey);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const replyFragment = await wrapMessageForReply({message, usingMids: [message.mid]});
|
const replyFragment = await wrapMessageForReply({message, usingMids: [message.mid]});
|
||||||
this.setTopInfo('edit', f, i18n('AccDescrEditing'), replyFragment, input, message);
|
this.setTopInfo('edit', f, i18n('AccDescrEditing'), replyFragment, input, message);
|
||||||
|
|
||||||
this.editMsgId = mid;
|
this.editMsgId = mid;
|
||||||
this.editMessage = message;
|
this.editMessage = message;
|
||||||
input = undefined;
|
input = undefined;
|
||||||
|
|
||||||
|
this.restoreInputLock = restoreInputLock;
|
||||||
};
|
};
|
||||||
f();
|
f();
|
||||||
}
|
}
|
||||||
|
@ -3084,6 +3118,11 @@ export default class ChatInput {
|
||||||
this.editMsgId = this.editMessage = undefined;
|
this.editMsgId = this.editMessage = undefined;
|
||||||
this.helperType = this.helperFunc = undefined;
|
this.helperType = this.helperFunc = undefined;
|
||||||
|
|
||||||
|
if(this.restoreInputLock) {
|
||||||
|
this.restoreInputLock?.();
|
||||||
|
this.restoreInputLock = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
if(this.chat.container.classList.contains('is-helper-active')) {
|
if(this.chat.container.classList.contains('is-helper-active')) {
|
||||||
appNavigationController.removeByType('input-helper');
|
appNavigationController.removeByType('input-helper');
|
||||||
this.chat.container.classList.remove('is-helper-active');
|
this.chat.container.classList.remove('is-helper-active');
|
||||||
|
@ -3139,7 +3178,7 @@ export default class ChatInput {
|
||||||
const oldReply = replyParent.lastElementChild.previousElementSibling;
|
const oldReply = replyParent.lastElementChild.previousElementSibling;
|
||||||
const haveReply = oldReply.classList.contains('reply');
|
const haveReply = oldReply.classList.contains('reply');
|
||||||
|
|
||||||
this.replyElements.iconBtn.replaceWith(this.replyElements.iconBtn = ButtonIcon((type === 'webpage' ? 'link' : type) + ' active reply-icon', {noRipple: true}));
|
this.replyElements.iconBtn.replaceWith(this.replyElements.iconBtn = this.createButtonIcon((type === 'webpage' ? 'link' : type) + ' active reply-icon', {noRipple: true}));
|
||||||
const {container} = wrapReply(title, subtitle, this.chat.animationGroup, message);
|
const {container} = wrapReply(title, subtitle, this.chat.animationGroup, message);
|
||||||
if(haveReply) {
|
if(haveReply) {
|
||||||
oldReply.replaceWith(container);
|
oldReply.replaceWith(container);
|
||||||
|
|
|
@ -209,7 +209,7 @@ export class AppSidebarLeft extends SidebarSlider {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
verify: () => App.isMainDomain
|
verify: () => App.isMainDomain
|
||||||
}, {
|
}, /* {
|
||||||
icon: 'char w',
|
icon: 'char w',
|
||||||
text: 'ChatList.Menu.SwitchTo.Webogram',
|
text: 'ChatList.Menu.SwitchTo.Webogram',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
|
@ -218,7 +218,7 @@ export class AppSidebarLeft extends SidebarSlider {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
verify: () => App.isMainDomain
|
verify: () => App.isMainDomain
|
||||||
}, {
|
}, */ {
|
||||||
icon: 'download',
|
icon: 'download',
|
||||||
text: 'PWA.Install',
|
text: 'PWA.Install',
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
|
|
|
@ -45,6 +45,7 @@ import PopupStickers from '../popups/stickers';
|
||||||
import {hideToast, toastNew} from '../toast';
|
import {hideToast, toastNew} from '../toast';
|
||||||
import wrapStickerAnimation from './stickerAnimation';
|
import wrapStickerAnimation from './stickerAnimation';
|
||||||
import framesCache from '../../helpers/framesCache';
|
import framesCache from '../../helpers/framesCache';
|
||||||
|
import {IS_MOBILE} from '../../environment/userAgent';
|
||||||
|
|
||||||
// https://github.com/telegramdesktop/tdesktop/blob/master/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp#L40
|
// https://github.com/telegramdesktop/tdesktop/blob/master/Telegram/SourceFiles/history/view/media/history_view_sticker.cpp#L40
|
||||||
export const STICKER_EFFECT_MULTIPLIER = 1 + 0.245 * 2;
|
export const STICKER_EFFECT_MULTIPLIER = 1 + 0.245 * 2;
|
||||||
|
@ -753,6 +754,11 @@ export async function onEmojiStickerClick({event, container, managers, peerId, m
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const activeAnimations: Set<{}> = (container as any).activeAnimations ??= new Set();
|
||||||
|
if(activeAnimations.size >= (IS_MOBILE ? 3 : 5)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const doc = await managers.appStickersManager.getAnimatedEmojiSticker(emoji, true);
|
const doc = await managers.appStickersManager.getAnimatedEmojiSticker(emoji, true);
|
||||||
if(!doc) {
|
if(!doc) {
|
||||||
return;
|
return;
|
||||||
|
@ -763,7 +769,7 @@ export async function onEmojiStickerClick({event, container, managers, peerId, m
|
||||||
v: 1
|
v: 1
|
||||||
};
|
};
|
||||||
|
|
||||||
const sendInteractionThrottled: () => void = (container as any).sendInteractionThrottled = throttle(() => {
|
const sendInteractionThrottled: () => void = (container as any).sendInteractionThrottled ??= throttle(() => {
|
||||||
const length = data.a.length;
|
const length = data.a.length;
|
||||||
if(!length) {
|
if(!length) {
|
||||||
return;
|
return;
|
||||||
|
@ -789,6 +795,9 @@ export async function onEmojiStickerClick({event, container, managers, peerId, m
|
||||||
data.a.length = 0;
|
data.a.length = 0;
|
||||||
}, 1000, false);
|
}, 1000, false);
|
||||||
|
|
||||||
|
const o = {};
|
||||||
|
activeAnimations.add(o);
|
||||||
|
|
||||||
const isOut = bubble ? bubble.classList.contains('is-out') : undefined;
|
const isOut = bubble ? bubble.classList.contains('is-out') : undefined;
|
||||||
const {animationDiv} = wrapStickerAnimation({
|
const {animationDiv} = wrapStickerAnimation({
|
||||||
doc,
|
doc,
|
||||||
|
@ -797,7 +806,10 @@ export async function onEmojiStickerClick({event, container, managers, peerId, m
|
||||||
size: 360,
|
size: 360,
|
||||||
target: container,
|
target: container,
|
||||||
play: true,
|
play: true,
|
||||||
withRandomOffset: true
|
withRandomOffset: true,
|
||||||
|
onUnmount: () => {
|
||||||
|
activeAnimations.delete(o);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if(isOut !== undefined && !isOut) {
|
if(isOut !== undefined && !isOut) {
|
||||||
|
|
|
@ -30,7 +30,8 @@ export default function wrapStickerAnimation({
|
||||||
fullThumb,
|
fullThumb,
|
||||||
withRandomOffset,
|
withRandomOffset,
|
||||||
relativeEffect,
|
relativeEffect,
|
||||||
loopEffect
|
loopEffect,
|
||||||
|
onUnmount
|
||||||
}: {
|
}: {
|
||||||
size: number,
|
size: number,
|
||||||
doc: MyDocument,
|
doc: MyDocument,
|
||||||
|
@ -43,7 +44,8 @@ export default function wrapStickerAnimation({
|
||||||
fullThumb?: PhotoSize | Extract<VideoSize, VideoSize.videoSize>,
|
fullThumb?: PhotoSize | Extract<VideoSize, VideoSize.videoSize>,
|
||||||
withRandomOffset?: boolean,
|
withRandomOffset?: boolean,
|
||||||
relativeEffect?: boolean,
|
relativeEffect?: boolean,
|
||||||
loopEffect?: boolean
|
loopEffect?: boolean,
|
||||||
|
onUnmount?: () => void
|
||||||
}) {
|
}) {
|
||||||
const animationDiv = document.createElement('div');
|
const animationDiv = document.createElement('div');
|
||||||
animationDiv.classList.add('emoji-animation');
|
animationDiv.classList.add('emoji-animation');
|
||||||
|
@ -58,6 +60,7 @@ export default function wrapStickerAnimation({
|
||||||
animation?.remove();
|
animation?.remove();
|
||||||
animationDiv.remove();
|
animationDiv.remove();
|
||||||
appImManager.chat.bubbles.scrollable.container.removeEventListener('scroll', onScroll);
|
appImManager.chat.bubbles.scrollable.container.removeEventListener('scroll', onScroll);
|
||||||
|
onUnmount?.();
|
||||||
};
|
};
|
||||||
|
|
||||||
const middlewareHelper = middleware?.create() ?? getMiddleware();
|
const middlewareHelper = middleware?.create() ?? getMiddleware();
|
||||||
|
|
|
@ -1061,13 +1061,14 @@ export class AppImManager extends EventListenerBase<{
|
||||||
|
|
||||||
if(
|
if(
|
||||||
chat?.input?.messageInput &&
|
chat?.input?.messageInput &&
|
||||||
e.target !== chat.input.messageInput &&
|
target !== chat.input.messageInput &&
|
||||||
target.tagName !== 'INPUT' &&
|
target.tagName !== 'INPUT' &&
|
||||||
!target.isContentEditable &&
|
!target.isContentEditable &&
|
||||||
!IS_TOUCH_SUPPORTED &&
|
!IS_TOUCH_SUPPORTED &&
|
||||||
(!mediaSizes.isMobile || this.tabId === APP_TABS.CHAT) &&
|
(!mediaSizes.isMobile || this.tabId === APP_TABS.CHAT) &&
|
||||||
!chat.selection.isSelecting &&
|
!chat.selection.isSelecting &&
|
||||||
!chat.input.recording
|
!chat.input.recording &&
|
||||||
|
chat.input.messageInput.isContentEditable
|
||||||
) {
|
) {
|
||||||
chat.input.messageInput.focus();
|
chat.input.messageInput.focus();
|
||||||
placeCaretAtEnd(chat.input.messageInput);
|
placeCaretAtEnd(chat.input.messageInput);
|
||||||
|
|
|
@ -3440,9 +3440,15 @@ export class AppMessagesManager extends AppManager {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const canEditMessageInPeer = this.appPeersManager.isBroadcast(message.peerId) ?
|
const {peerId} = message;
|
||||||
this.appChatsManager.hasRights(message.peerId.toChatId(), 'edit_messages') :
|
|
||||||
message.pFlags.out;
|
const canEditMessageInPeer = this.appPeersManager.isBroadcast(peerId) ?
|
||||||
|
this.appChatsManager.hasRights(peerId.toChatId(), 'edit_messages') :
|
||||||
|
(
|
||||||
|
peerId.isAnyChat() && kind === 'text' ?
|
||||||
|
this.appChatsManager.hasRights(peerId.toChatId(), 'send_plain') || this.appChatsManager.hasRights(peerId.toChatId(), 'send_media') :
|
||||||
|
true
|
||||||
|
) && message.pFlags.out;
|
||||||
|
|
||||||
if(
|
if(
|
||||||
!canEditMessageInPeer || (
|
!canEditMessageInPeer || (
|
||||||
|
|
Loading…
Reference in New Issue