Browse Source

Forward options

Fixed opening scheduled chat after forwarding
master
morethanwords 3 years ago
parent
commit
31fcc95989
  1. 43
      src/components/buttonMenu.ts
  2. 60
      src/components/chat/bubbles.ts
  3. 310
      src/components/chat/input.ts
  4. 2
      src/components/chat/replyContainer.ts
  5. 4
      src/components/chat/selection.ts
  6. 1
      src/components/checkboxField.ts
  7. 6
      src/components/radioForm.ts
  8. 2
      src/config/app.ts
  9. 27
      src/lang.ts
  10. 38
      src/lib/appManagers/appMessagesManager.ts
  11. 4
      src/lib/mediaPlayer.ts
  12. 9
      src/scss/partials/_button.scss
  13. 54
      src/scss/partials/_chat.scss
  14. 1
      src/scss/partials/_chatBubble.scss
  15. 3
      src/scss/partials/_checkbox.scss
  16. 5
      src/scss/partials/_poll.scss

43
src/components/buttonMenu.ts

@ -16,10 +16,12 @@ export type ButtonMenuItemOptions = {
icon?: string, icon?: string,
text?: LangPackKey, text?: LangPackKey,
regularText?: string, regularText?: string,
onClick: (e: MouseEvent | TouchEvent) => void, onClick: (e: MouseEvent | TouchEvent) => void | boolean,
element?: HTMLElement, element?: HTMLElement,
textElement?: HTMLElement,
options?: AttachClickOptions, options?: AttachClickOptions,
checkboxField?: CheckboxField, checkboxField?: CheckboxField,
noCheckboxClickListener?: boolean,
keepOpen?: boolean keepOpen?: boolean
/* , cancelEvent?: true */ /* , cancelEvent?: true */
}; };
@ -27,34 +29,43 @@ export type ButtonMenuItemOptions = {
const ButtonMenuItem = (options: ButtonMenuItemOptions) => { const ButtonMenuItem = (options: ButtonMenuItemOptions) => {
if(options.element) return options.element; if(options.element) return options.element;
const {icon, text, onClick} = options; const {icon, text, onClick, checkboxField, noCheckboxClickListener} = options;
const el = document.createElement('div'); const el = document.createElement('div');
el.className = 'btn-menu-item' + (icon ? ' tgico-' + icon : ''); el.className = 'btn-menu-item' + (icon ? ' tgico-' + icon : '');
ripple(el); ripple(el);
const t = text ? i18n(text) : document.createElement('span'); let textElement = options.textElement;
if(options.regularText) t.innerHTML = options.regularText; if(!textElement) {
t.classList.add('btn-menu-item-text'); textElement = options.textElement = text ? i18n(text) : document.createElement('span');
el.append(t); if(options.regularText) textElement.innerHTML = options.regularText;
if(options.checkboxField) {
el.append(options.checkboxField.label);
attachClickEvent(el, () => {
options.checkboxField.checked = !options.checkboxField.checked;
}, options.options);
} }
textElement.classList.add('btn-menu-item-text');
el.append(textElement);
const keepOpen = !!options.checkboxField || !!options.keepOpen; const keepOpen = !!checkboxField || !!options.keepOpen;
// * cancel mobile keyboard close // * cancel mobile keyboard close
attachClickEvent(el, CLICK_EVENT_NAME !== 'click' || keepOpen ? (e) => { attachClickEvent(el, /* CLICK_EVENT_NAME !== 'click' || keepOpen ? */ (e) => {
cancelEvent(e); cancelEvent(e);
onClick(e); const result = onClick(e);
if(result === false) {
return;
}
if(!keepOpen) { if(!keepOpen) {
closeBtnMenu(); closeBtnMenu();
} }
} : onClick, options.options);
if(checkboxField && !noCheckboxClickListener/* && result !== false */) {
checkboxField.checked = checkboxField.input.type === 'radio' ? true : !checkboxField.checked;
}
}/* : onClick */, options.options);
if(checkboxField) {
el.append(checkboxField.label);
}
return options.element = el; return options.element = el;
}; };

60
src/components/chat/bubbles.ts

@ -1854,12 +1854,18 @@ export default class ChatBubbles {
return {cached: true, promise: this.chat.setPeerPromise}; return {cached: true, promise: this.chat.setPeerPromise};
} */ } */
const chatType = this.chat.type;
if(chatType === 'scheduled') {
lastMsgId = 0;
}
this.historyStorage = this.appMessagesManager.getHistoryStorage(peerId, this.chat.threadId); this.historyStorage = this.appMessagesManager.getHistoryStorage(peerId, this.chat.threadId);
let topMessage = this.chat.type === 'pinned' ? this.appMessagesManager.pinnedMessages[peerId].maxId : this.historyStorage.maxId ?? 0; let topMessage = chatType === 'pinned' ? this.appMessagesManager.pinnedMessages[peerId].maxId : this.historyStorage.maxId ?? 0;
const isTarget = lastMsgId !== undefined; const isTarget = lastMsgId !== undefined;
// * this one will fix topMessage for null message in history (e.g. channel comments with only 1 comment and it is a topMessage) // * this one will fix topMessage for null message in history (e.g. channel comments with only 1 comment and it is a topMessage)
/* if(this.chat.type !== 'pinned' && topMessage && !historyStorage.history.slice.includes(topMessage)) { /* if(chatType !== 'pinned' && topMessage && !historyStorage.history.slice.includes(topMessage)) {
topMessage = 0; topMessage = 0;
} */ } */
@ -1883,6 +1889,8 @@ export default class ChatBubbles {
} }
const isJump = lastMsgId !== topMessage; const isJump = lastMsgId !== topMessage;
const {scrollable} = this;
if(samePeer) { if(samePeer) {
const mounted = this.getMountedBubble(lastMsgId); const mounted = this.getMountedBubble(lastMsgId);
@ -1893,7 +1901,7 @@ export default class ChatBubbles {
this.chat.dispatchEvent('setPeer', lastMsgId, false); this.chat.dispatchEvent('setPeer', lastMsgId, false);
} else if(topMessage && !isJump) { } else if(topMessage && !isJump) {
//this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight); //this.log('will scroll down', this.scroll.scrollTop, this.scroll.scrollHeight);
this.scrollable.scrollTop = this.scrollable.scrollHeight; scrollable.scrollTop = scrollable.scrollHeight;
this.chat.dispatchEvent('setPeer', lastMsgId, true); this.chat.dispatchEvent('setPeer', lastMsgId, true);
} }
@ -1909,16 +1917,16 @@ export default class ChatBubbles {
this.replyFollowHistory.length = 0; this.replyFollowHistory.length = 0;
this.passEntities = { this.passEntities = {
messageEntityBotCommand: this.appPeersManager.isAnyGroup(this.peerId) || this.appUsersManager.isBot(this.peerId) messageEntityBotCommand: this.appPeersManager.isAnyGroup(peerId) || this.appUsersManager.isBot(peerId)
}; };
} }
if(DEBUG) { if(DEBUG) {
this.log('setPeer peerId:', this.peerId, this.historyStorage, lastMsgId, topMessage); this.log('setPeer peerId:', peerId, this.historyStorage, lastMsgId, topMessage);
} }
// add last message, bc in getHistory will load < max_id // add last message, bc in getHistory will load < max_id
const additionMsgId = isJump || this.chat.type === 'scheduled' ? 0 : topMessage; const additionMsgId = isJump || chatType === 'scheduled' ? 0 : topMessage;
/* this.setPeerPromise = null; /* this.setPeerPromise = null;
this.preloader.detach(); this.preloader.detach();
@ -1943,12 +1951,12 @@ export default class ChatBubbles {
const oldChatInner = this.chatInner; const oldChatInner = this.chatInner;
this.cleanup(); this.cleanup();
this.chatInner = document.createElement('div'); const chatInner = this.chatInner = document.createElement('div');
if(samePeer) { if(samePeer) {
this.chatInner.className = oldChatInner.className; chatInner.className = oldChatInner.className;
this.chatInner.classList.remove('disable-hover', 'is-scrolling'); chatInner.classList.remove('disable-hover', 'is-scrolling');
} else { } else {
this.chatInner.classList.add('bubbles-inner'); chatInner.classList.add('bubbles-inner');
} }
this.lazyLoadQueue.lock(); this.lazyLoadQueue.lock();
@ -1970,7 +1978,7 @@ export default class ChatBubbles {
// clear // clear
if(!cached) { if(!cached) {
if(!samePeer) { if(!samePeer) {
this.scrollable.container.textContent = ''; scrollable.container.textContent = '';
//oldChatInner.remove(); //oldChatInner.remove();
this.chat.finishPeerChange(isTarget, isJump, lastMsgId); this.chat.finishPeerChange(isTarget, isJump, lastMsgId);
this.preloader.attach(this.bubblesContainer); this.preloader.attach(this.bubblesContainer);
@ -2000,9 +2008,9 @@ export default class ChatBubbles {
// this.ladderDeferred.resolve(); // this.ladderDeferred.resolve();
this.scrollable.lastScrollDirection = 0; scrollable.lastScrollDirection = 0;
this.scrollable.lastScrollTop = 0; scrollable.lastScrollTop = 0;
replaceContent(this.scrollable.container, this.chatInner); replaceContent(scrollable.container, chatInner);
animationIntersector.unlockGroup(CHAT_ANIMATION_GROUP); animationIntersector.unlockGroup(CHAT_ANIMATION_GROUP);
animationIntersector.checkAnimations(false, CHAT_ANIMATION_GROUP/* , true */); animationIntersector.checkAnimations(false, CHAT_ANIMATION_GROUP/* , true */);
@ -2013,7 +2021,7 @@ export default class ChatBubbles {
//if(dialog && lastMsgID && lastMsgID !== topMessage && (this.bubbles[lastMsgID] || this.firstUnreadBubble)) { //if(dialog && lastMsgID && lastMsgID !== topMessage && (this.bubbles[lastMsgID] || this.firstUnreadBubble)) {
if(savedPosition) { if(savedPosition) {
this.scrollable.scrollTop = savedPosition.top; scrollable.scrollTop = savedPosition.top;
/* const mountedByLastMsgId = this.getMountedBubble(lastMsgId); /* const mountedByLastMsgId = this.getMountedBubble(lastMsgId);
let bubble: HTMLElement = mountedByLastMsgId?.bubble; let bubble: HTMLElement = mountedByLastMsgId?.bubble;
if(!bubble?.parentElement) { if(!bubble?.parentElement) {
@ -2023,15 +2031,15 @@ export default class ChatBubbles {
if(bubble) { if(bubble) {
const top = bubble.getBoundingClientRect().top; const top = bubble.getBoundingClientRect().top;
const distance = savedPosition.top - top; const distance = savedPosition.top - top;
this.scrollable.scrollTop += distance; scrollable.scrollTop += distance;
} */ } */
} else if((topMessage && isJump) || isTarget) { } else if((topMessage && isJump) || isTarget) {
const fromUp = maxBubbleId > 0 && (maxBubbleId < lastMsgId || lastMsgId < 0); const fromUp = maxBubbleId > 0 && (maxBubbleId < lastMsgId || lastMsgId < 0);
const followingUnread = readMaxId === lastMsgId && !isTarget; const followingUnread = readMaxId === lastMsgId && !isTarget;
if(!fromUp && samePeer) { if(!fromUp && samePeer) {
this.scrollable.scrollTop = 99999; scrollable.scrollTop = 99999;
} else if(fromUp/* && (samePeer || forwardingUnread) */) { } else if(fromUp/* && (samePeer || forwardingUnread) */) {
this.scrollable.scrollTop = 0; scrollable.scrollTop = 0;
} }
const mountedByLastMsgId = this.getMountedBubble(lastMsgId); const mountedByLastMsgId = this.getMountedBubble(lastMsgId);
@ -2048,7 +2056,7 @@ export default class ChatBubbles {
} }
} }
} else { } else {
this.scrollable.scrollTop = 99999; scrollable.scrollTop = 99999;
} }
this.onScroll(); this.onScroll();
@ -2056,7 +2064,7 @@ export default class ChatBubbles {
const middleware = this.getMiddleware(); const middleware = this.getMiddleware();
const afterSetPromise = Promise.all([setPeerPromise, getHeavyAnimationPromise()]); const afterSetPromise = Promise.all([setPeerPromise, getHeavyAnimationPromise()]);
afterSetPromise.then(() => { // check whether list isn't full afterSetPromise.then(() => { // check whether list isn't full
this.scrollable.checkForTriggers(); scrollable.checkForTriggers();
}); });
this.chat.dispatchEvent('setPeer', lastMsgId, !isJump); this.chat.dispatchEvent('setPeer', lastMsgId, !isJump);
@ -2074,7 +2082,7 @@ export default class ChatBubbles {
return; return;
} }
this.scrollable.checkForTriggers(); scrollable.checkForTriggers();
if(needFetchInterval) { if(needFetchInterval) {
const f = () => { const f = () => {
@ -2092,7 +2100,7 @@ export default class ChatBubbles {
const slice = historyStorage.history.slice; const slice = historyStorage.history.slice;
const isBottomEnd = slice.isEnd(SliceEnd.Bottom); const isBottomEnd = slice.isEnd(SliceEnd.Bottom);
if(this.scrollable.loadedAll.bottom && this.scrollable.loadedAll.bottom !== isBottomEnd) { if(scrollable.loadedAll.bottom && scrollable.loadedAll.bottom !== isBottomEnd) {
this.setLoaded('bottom', isBottomEnd); this.setLoaded('bottom', isBottomEnd);
this.onScroll(); this.onScroll();
} }
@ -2114,14 +2122,14 @@ export default class ChatBubbles {
}); });
} }
this.log('scrolledAllDown:', this.scrollable.loadedAll.bottom); this.log('scrolledAllDown:', scrollable.loadedAll.bottom);
//if(!this.unreaded.length && dialog) { // lol //if(!this.unreaded.length && dialog) { // lol
if(this.scrollable.loadedAll.bottom && topMessage && !this.unreaded.size) { // lol if(scrollable.loadedAll.bottom && topMessage && !this.unreaded.size) { // lol
this.onScrolledAllDown(); this.onScrolledAllDown();
} }
if(this.chat.type === 'chat') { if(chatType === 'chat') {
const dialog = this.appMessagesManager.getDialogOnly(peerId); const dialog = this.appMessagesManager.getDialogOnly(peerId);
if(dialog?.pFlags.unread_mark) { if(dialog?.pFlags.unread_mark) {
this.appMessagesManager.markDialogUnread(peerId, true); this.appMessagesManager.markDialogUnread(peerId, true);
@ -2624,7 +2632,7 @@ export default class ChatBubbles {
if(message.pFlags.unread || isOutgoing) this.unreadOut.add(message.mid); if(message.pFlags.unread || isOutgoing) this.unreadOut.add(message.mid);
let status = ''; let status = '';
if(isOutgoing) status = 'is-sending'; if(isOutgoing) status = 'is-sending';
else status = message.pFlags.unread ? 'is-sent' : 'is-read'; else status = message.pFlags.unread || message.pFlags.is_scheduled ? 'is-sent' : 'is-read';
bubble.classList.add(status); bubble.classList.add(status);
} }

310
src/components/chat/input.ts

@ -70,7 +70,7 @@ import ReplyKeyboard from './replyKeyboard';
import InlineHelper from './inlineHelper'; import InlineHelper from './inlineHelper';
import debounce from '../../helpers/schedulers/debounce'; import debounce from '../../helpers/schedulers/debounce';
import noop from '../../helpers/noop'; import noop from '../../helpers/noop';
import { putPreloader } from '../misc'; import { openBtnMenu, putPreloader } from '../misc';
import SetTransition from '../singleTransition'; import SetTransition from '../singleTransition';
import PeerTitle from '../peerTitle'; import PeerTitle from '../peerTitle';
import { fastRaf } from '../../helpers/schedulers'; import { fastRaf } from '../../helpers/schedulers';
@ -84,6 +84,10 @@ import { NULL_PEER_ID } from '../../lib/mtproto/mtproto_config';
import setCaretAt from '../../helpers/dom/setCaretAt'; import setCaretAt from '../../helpers/dom/setCaretAt';
import getKeyFromEvent from '../../helpers/dom/getKeyFromEvent'; import getKeyFromEvent from '../../helpers/dom/getKeyFromEvent';
import getKeyFromEventCaseInsensitive from '../../helpers/dom/getKeyFromEventCaseInsensitive'; import getKeyFromEventCaseInsensitive from '../../helpers/dom/getKeyFromEventCaseInsensitive';
import CheckboxField from '../checkboxField';
import DropdownHover from '../../helpers/dropdownHover';
import RadioForm from '../radioForm';
import findUpTag from '../../helpers/dom/findUpTag';
const RECORD_MIN_TIME = 500; const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.'; const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@ -123,7 +127,17 @@ export default class ChatInput {
iconBtn: HTMLButtonElement iconBtn: HTMLButtonElement
} = {} as any; } = {} as any;
private forwardElements: {} = {} as any; private forwardElements: {
changePeer: ButtonMenuItemOptions,
showSender: ButtonMenuItemOptions,
hideSender: ButtonMenuItemOptions,
showCaption: ButtonMenuItemOptions,
hideCaption: ButtonMenuItemOptions,
container: HTMLElement,
modifyArgs?: ButtonMenuItemOptions[]
} = {} as any;
private forwardHover: DropdownHover;
private forwardWasDroppingAuthor: boolean;
private getWebPagePromise: Promise<void>; private getWebPagePromise: Promise<void>;
private willSendWebPage: WebPage = null; private willSendWebPage: WebPage = null;
@ -313,14 +327,119 @@ export default class ChatInput {
this.replyElements.container.append(this.replyElements.iconBtn, this.replyElements.cancelBtn); this.replyElements.container.append(this.replyElements.iconBtn, this.replyElements.cancelBtn);
const forwardBtnMenu = ButtonMenu([], this.listenerSetter); //
const onHideAuthorClick = () => {
isChangingAuthor = true;
return this.canToggleHideAuthor();
};
const onHideCaptionClick = () => {
isChangingAuthor = false;
};
const forwardElements: ChatInput['forwardElements'] = this.forwardElements = {} as any;
let isChangingAuthor = false;
const forwardButtons: ButtonMenuItemOptions[] = [
forwardElements.showSender = {
text: 'Chat.Alert.Forward.Action.Show1',
onClick: onHideAuthorClick,
checkboxField: new CheckboxField({checked: true})
},
forwardElements.hideSender = {
text: 'Chat.Alert.Forward.Action.Hide1',
onClick: onHideAuthorClick,
checkboxField: new CheckboxField({checked: false})
},
forwardElements.showCaption = {
text: 'Chat.Alert.Forward.Action.ShowCaption',
onClick: onHideCaptionClick,
checkboxField: new CheckboxField({checked: true})
},
forwardElements.hideCaption = {
text: 'Chat.Alert.Forward.Action.HideCaption',
onClick: onHideCaptionClick,
checkboxField: new CheckboxField({checked: false})
},
forwardElements.changePeer = {
text: 'Chat.Alert.Forward.Action.Another',
onClick: () => {
this.changeForwardRecipient();
},
icon: 'replace'
}
];
const forwardBtnMenu = forwardElements.container = ButtonMenu(forwardButtons, this.listenerSetter);
// forwardBtnMenu.classList.add('top-center');
const children = Array.from(forwardBtnMenu.children) as HTMLElement[];
const groups: {
elements: HTMLElement[],
onChange: (value: string, event: Event) => void
}[] = [{
elements: children.slice(0, 2),
onChange: (value, e) => {
const checked = !!+value;
if(isChangingAuthor) {
this.forwardWasDroppingAuthor = !checked;
}
const replyTitle = this.replyElements.container.querySelector('.reply-title');
if(replyTitle) {
const el = replyTitle.firstElementChild as HTMLElement;
const i = I18n.weakMap.get(el) as I18n.IntlElement;
const langPackKey: LangPackKey = forwardElements.showSender.checkboxField.checked ? 'Chat.Accessory.Forward' : 'Chat.Accessory.Hidden';
i.key = langPackKey;
i.update();
}
}
}, {
elements: children.slice(2, 4),
onChange: (value) => {
const checked = !!+value;
let b: ButtonMenuItemOptions;
if(checked && this.forwardWasDroppingAuthor !== undefined) {
b = this.forwardWasDroppingAuthor ? forwardElements.hideSender : forwardElements.showSender;
} else {
b = checked ? forwardElements.showSender : forwardElements.hideSender;
}
b.checkboxField.checked = true;
}
}];
groups.forEach(group => {
const container = RadioForm(group.elements.map(e => {
return {
container: e,
input: e.querySelector('input')
};
}), group.onChange);
const hr = document.createElement('hr');
container.append(hr);
forwardBtnMenu.append(container);
});
this.forwardElements = { forwardBtnMenu.append(forwardElements.changePeer.element);
container: forwardBtnMenu
} as any;
if(!IS_TOUCH_SUPPORTED) {
const forwardHover = this.forwardHover = new DropdownHover({
element: forwardBtnMenu
});
}
forwardElements.modifyArgs = forwardButtons.slice(0, -1);
this.replyElements.container.append(forwardBtnMenu); this.replyElements.container.append(forwardBtnMenu);
forwardElements.modifyArgs.forEach((b, idx) => {
const {input} = b.checkboxField;
input.type = 'radio';
input.name = idx < 2 ? 'author' : 'caption';
input.value = '' + +!(idx % 2);
});
//
this.newMessageWrapper = document.createElement('div'); this.newMessageWrapper = document.createElement('div');
this.newMessageWrapper.classList.add('new-message-wrapper'); this.newMessageWrapper.classList.add('new-message-wrapper');
@ -467,7 +586,7 @@ export default class ChatInput {
openSide: 'top-left', openSide: 'top-left',
onContextElement: this.btnSend, onContextElement: this.btnSend,
onOpen: () => { onOpen: () => {
return !this.isInputEmpty(); return !this.isInputEmpty() || !!Object.keys(this.forwarding).length;
} }
}); });
@ -706,9 +825,15 @@ export default class ChatInput {
} }
public scheduleSending = (callback: () => void = this.sendMessage.bind(this, true), initDate = new Date()) => { public scheduleSending = (callback: () => void = this.sendMessage.bind(this, true), initDate = new Date()) => {
const canSendWhenOnline = rootScope.myId !== this.chat.peerId && this.chat.peerId.isUser() && this.appUsersManager.isUserOnlineVisible(this.chat.peerId); const {peerId} = this.chat;
const middleware = this.chat.bubbles.getMiddleware();
const canSendWhenOnline = rootScope.myId !== peerId && peerId.isUser() && this.appUsersManager.isUserOnlineVisible(peerId);
new PopupSchedule(initDate, (timestamp) => { new PopupSchedule(initDate, (timestamp) => {
if(!middleware()) {
return;
}
const minTimestamp = (Date.now() / 1000 | 0) + 10; const minTimestamp = (Date.now() / 1000 | 0) + 10;
if(timestamp <= minTimestamp) { if(timestamp <= minTimestamp) {
timestamp = undefined; timestamp = undefined;
@ -718,7 +843,13 @@ export default class ChatInput {
callback(); callback();
if(this.chat.type !== 'scheduled' && timestamp) { if(this.chat.type !== 'scheduled' && timestamp) {
this.appImManager.openScheduled(this.chat.peerId); setTimeout(() => { // ! need timeout here because .forwardMessages will be called after timeout
if(!middleware()) {
return;
}
this.appImManager.openScheduled(peerId);
}, 0);
} }
}, canSendWhenOnline).show(); }, canSendWhenOnline).show();
}; };
@ -842,6 +973,12 @@ export default class ChatInput {
}/* else if(this.chat.type === 'chat') { }/* else if(this.chat.type === 'chat') {
} */ } */
if(this.forwardElements) {
this.forwardWasDroppingAuthor = false;
this.forwardElements.showCaption.checkboxField.setValueSilently(true);
this.forwardElements.showSender.checkboxField.setValueSilently(true);
}
if(this.btnScheduled) { if(this.btnScheduled) {
this.btnScheduled.classList.add('hide'); this.btnScheduled.classList.add('hide');
const middleware = this.chat.bubbles.getMiddleware(); const middleware = this.chat.bubbles.getMiddleware();
@ -1668,26 +1805,11 @@ export default class ChatInput {
private onHelperClick = (e: Event) => { private onHelperClick = (e: Event) => {
cancelEvent(e); cancelEvent(e);
if(!findUpClassName(e.target, 'reply-wrapper')) return; if(!findUpClassName(e.target, 'reply')) return;
if(this.helperType === 'forward') { if(this.helperType === 'forward') {
if(this.helperWaitingForward) return; if(IS_TOUCH_SUPPORTED && !this.forwardElements.container.classList.contains('active')) {
this.helperWaitingForward = true; openBtnMenu(this.forwardElements.container);
}
const helperFunc = this.helperFunc;
this.clearHelper();
this.updateSendBtn();
let selected = false;
const popup = new PopupForward(copy(this.forwarding), () => {
selected = true;
});
popup.addEventListener('close', () => {
this.helperWaitingForward = false;
if(!selected) {
helperFunc();
}
});
} else if(this.helperType === 'reply') { } else if(this.helperType === 'reply') {
this.chat.setMessageId(this.replyToMsgId); this.chat.setMessageId(this.replyToMsgId);
} else if(this.helperType === 'edit') { } else if(this.helperType === 'edit') {
@ -1695,6 +1817,27 @@ export default class ChatInput {
} }
}; };
private changeForwardRecipient() {
if(this.helperWaitingForward) return;
this.helperWaitingForward = true;
const helperFunc = this.helperFunc;
this.clearHelper();
this.updateSendBtn();
let selected = false;
const popup = new PopupForward(copy(this.forwarding), () => {
selected = true;
});
popup.addEventListener('close', () => {
this.helperWaitingForward = false;
if(!selected) {
helperFunc();
}
});
}
public clearInput(canSetDraft = true, fireEvent = true, clearValue = '') { public clearInput(canSetDraft = true, fireEvent = true, clearValue = '') {
if(document.activeElement === this.messageInput && IS_MOBILE_SAFARI) { // fix first char uppercase if(document.activeElement === this.messageInput && IS_MOBILE_SAFARI) { // fix first char uppercase
const i = document.createElement('input'); const i = document.createElement('input');
@ -1787,37 +1930,41 @@ export default class ChatInput {
} }
public sendMessage(force = false) { public sendMessage(force = false) {
if(this.chat.type === 'scheduled' && !force && !this.editMsgId) { const {editMsgId, chat} = this;
if(chat.type === 'scheduled' && !force && !editMsgId) {
this.scheduleSending(); this.scheduleSending();
return; return;
} }
const {threadId, peerId} = chat;
const {replyToMsgId, noWebPage, sendSilent, scheduleDate} = this;
const {value, entities} = getRichValue(this.messageInputField.input); const {value, entities} = getRichValue(this.messageInputField.input);
//return; //return;
if(this.editMsgId) { if(editMsgId) {
const message = this.editMessage; const message = this.editMessage;
if(!!value.trim() || message.media) { if(value.trim() || message.media) {
this.appMessagesManager.editMessage(message, value, { this.appMessagesManager.editMessage(message, value, {
entities, entities,
noWebPage: this.noWebPage noWebPage: noWebPage
}); });
this.onMessageSent(); this.onMessageSent();
} else { } else {
new PopupDeleteMessages(this.chat.peerId, [this.editMsgId], this.chat.type); new PopupDeleteMessages(peerId, [editMsgId], chat.type);
return; return;
} }
} else { } else if(value.trim()) {
this.appMessagesManager.sendText(this.chat.peerId, value, { this.appMessagesManager.sendText(peerId, value, {
entities, entities,
replyToMsgId: this.replyToMsgId, replyToMsgId: replyToMsgId,
threadId: this.chat.threadId, threadId: threadId,
noWebPage: this.noWebPage, noWebPage: noWebPage,
webPage: this.getWebPagePromise ? undefined : this.willSendWebPage, webPage: this.getWebPagePromise ? undefined : this.willSendWebPage,
scheduleDate: this.scheduleDate, scheduleDate: scheduleDate,
silent: this.sendSilent, silent: sendSilent,
clearDraft: true clearDraft: true
}); });
@ -1828,14 +1975,13 @@ export default class ChatInput {
// * wait for sendText set messageId for invokeAfterMsg // * wait for sendText set messageId for invokeAfterMsg
if(this.forwarding) { if(this.forwarding) {
const forwarding = copy(this.forwarding); const forwarding = copy(this.forwarding);
const peerId = this.chat.peerId;
const silent = this.sendSilent;
const scheduleDate = this.scheduleDate;
setTimeout(() => { setTimeout(() => {
for(const fromPeerId in forwarding) { for(const fromPeerId in forwarding) {
this.appMessagesManager.forwardMessages(peerId, fromPeerId.toPeerId(), forwarding[fromPeerId], { this.appMessagesManager.forwardMessages(peerId, fromPeerId.toPeerId(), forwarding[fromPeerId], {
silent, silent: sendSilent,
scheduleDate: scheduleDate scheduleDate: scheduleDate,
dropAuthor: this.forwardElements.hideSender.checkboxField.checked,
dropCaptions: this.forwardElements.hideCaption.checkboxField.checked
}); });
} }
@ -1883,6 +2029,12 @@ export default class ChatInput {
return false; return false;
} }
private canToggleHideAuthor() {
const hideCaptionCheckboxField = this.forwardElements.hideCaption.checkboxField;
return !hideCaptionCheckboxField.checked ||
findUpTag(hideCaptionCheckboxField.label, 'FORM').classList.contains('hide');
}
/* public sendSomething(callback: () => void, force = false) { /* public sendSomething(callback: () => void, force = false) {
if(this.chat.type === 'scheduled' && !force) { if(this.chat.type === 'scheduled' && !force) {
this.scheduleSending(() => this.sendSomething(callback, true)); this.scheduleSending(() => this.sendSomething(callback, true));
@ -1915,7 +2067,7 @@ export default class ChatInput {
//const peerTitles: string[] //const peerTitles: string[]
const fromPeerIds = Object.keys(fromPeerIdsMids).map(fromPeerId => fromPeerId.toPeerId()); const fromPeerIds = Object.keys(fromPeerIdsMids).map(fromPeerId => fromPeerId.toPeerId());
const smth: Set<string> = new Set(); const smth: Set<string> = new Set();
let length = 0; let length = 0, messagesWithCaptionsLength = 0;
fromPeerIds.forEach(fromPeerId => { fromPeerIds.forEach(fromPeerId => {
const mids = fromPeerIdsMids[fromPeerId]; const mids = fromPeerIdsMids[fromPeerId];
@ -1926,6 +2078,10 @@ export default class ChatInput {
} else { } else {
smth.add('P' + message.fromId); smth.add('P' + message.fromId);
} }
if(message.media && message.message) {
++messagesWithCaptionsLength;
}
}); });
length += mids.length; length += mids.length;
@ -1935,19 +2091,25 @@ export default class ChatInput {
const peerTitles = [...smth].map(smth => { const peerTitles = [...smth].map(smth => {
const type = smth[0]; const type = smth[0];
smth = smth.slice(1); smth = smth.slice(1);
return type === 'P' ? if(type === 'P') {
new PeerTitle({peerId: smth.toPeerId(), dialog: false, onlyFirstName}).element : const peerId = smth.toPeerId();
(onlyFirstName ? smth.split(' ')[0] : smth); return peerId === rootScope.myId ? i18n('Chat.Accessory.Forward.You') : new PeerTitle({peerId, dialog: false, onlyFirstName}).element;
} else {
return onlyFirstName ? smth.split(' ')[0] : smth;
}
}); });
const title = document.createDocumentFragment(); const titleKey: LangPackKey = this.forwardElements.showSender.checkboxField.checked ? 'Chat.Accessory.Forward' : 'Chat.Accessory.Hidden';
const title = i18n(titleKey, [length]);
const senderTitles = document.createDocumentFragment();
if(peerTitles.length < 3) { if(peerTitles.length < 3) {
title.append(...join(peerTitles, false)); senderTitles.append(...join(peerTitles, false));
} else { } else {
title.append(peerTitles[0], i18n('AndOther', [peerTitles.length - 1])); senderTitles.append(peerTitles[0], i18n('AndOther', [peerTitles.length - 1]));
} }
let firstMessage: any, usingFullAlbum: boolean; let firstMessage: Message.message, usingFullAlbum: boolean;
if(fromPeerIds.length === 1) { if(fromPeerIds.length === 1) {
const fromPeerId = fromPeerIds[0]; const fromPeerId = fromPeerIds[0];
const mids = fromPeerIdsMids[fromPeerId]; const mids = fromPeerIdsMids[fromPeerId];
@ -1961,13 +2123,45 @@ export default class ChatInput {
} }
} }
} }
const subtitleFragment = document.createDocumentFragment();
const delimiter = ': ';
if(usingFullAlbum || length === 1) { if(usingFullAlbum || length === 1) {
const mids = fromPeerIdsMids[fromPeerIds[0]]; const mids = fromPeerIdsMids[fromPeerIds[0]];
const replyFragment = this.appMessagesManager.wrapMessageForReply(firstMessage, undefined, mids); const replyFragment = this.appMessagesManager.wrapMessageForReply(firstMessage, undefined, mids);
this.setTopInfo('forward', f, title, replyFragment); subtitleFragment.append(
senderTitles,
delimiter,
replyFragment
);
} else { } else {
this.setTopInfo('forward', f, title, i18n('ForwardedMessageCount', [length])); subtitleFragment.append(
i18n('Chat.Accessory.Forward.From'),
delimiter,
senderTitles
);
}
let newReply = this.setTopInfo('forward', f, title, subtitleFragment);
this.forwardElements.modifyArgs.forEach((b, idx) => {
const text = b.textElement;
const intl: I18n.IntlElement = I18n.weakMap.get(text) as any;
intl.args = [idx < 2 ? fromPeerIds.length : messagesWithCaptionsLength];
intl.update();
});
const form = findUpTag(this.forwardElements.showCaption.checkboxField.label, 'FORM');
form.classList.toggle('hide', !messagesWithCaptionsLength);
const hideCaption = this.forwardElements.hideCaption.checkboxField.checked;
if(messagesWithCaptionsLength && hideCaption) {
this.forwardElements.hideSender.checkboxField.setValueSilently(true);
} else if(this.forwardWasDroppingAuthor !== undefined) {
(this.forwardWasDroppingAuthor ? this.forwardElements.hideSender : this.forwardElements.showSender).checkboxField.setValueSilently(true);
}
if(this.forwardHover) {
this.forwardHover.attachButtonListener(newReply, this.listenerSetter);
} }
this.forwarding = fromPeerIdsMids; this.forwarding = fromPeerIdsMids;
@ -2114,6 +2308,8 @@ export default class ChatInput {
setTimeout(() => { setTimeout(() => {
this.updateSendBtn(); this.updateSendBtn();
}, 0); }, 0);
return newReply;
} }
// public saveScroll() { // public saveScroll() {

2
src/components/chat/replyContainer.ts

@ -44,7 +44,7 @@ export function wrapReplyDivAndCaption(options: {
let middleware: () => boolean; let middleware: () => boolean;
if(media && mediaEl) { if(media && mediaEl) {
subtitleEl.textContent = ''; subtitleEl.textContent = '';
subtitleEl.append(appMessagesManager.wrapMessageForReply(message)); subtitleEl.append(appMessagesManager.wrapMessageForReply(message, undefined, undefined, undefined, undefined, true));
//console.log('wrap reply', media); //console.log('wrap reply', media);

4
src/components/chat/selection.ts

@ -622,7 +622,7 @@ export class SearchSelection extends AppSelection {
attachClickEvent(this.selectionForwardBtn, () => { attachClickEvent(this.selectionForwardBtn, () => {
const obj: {[fromPeerId: PeerId]: number[]} = {}; const obj: {[fromPeerId: PeerId]: number[]} = {};
for(const [fromPeerId, mids] of this.selectedMids) { for(const [fromPeerId, mids] of this.selectedMids) {
obj[fromPeerId] = Array.from(mids); obj[fromPeerId] = Array.from(mids).sort((a, b) => a - b);
} }
new PopupForward(obj, () => { new PopupForward(obj, () => {
@ -897,7 +897,7 @@ export default class ChatSelection extends AppSelection {
attachClickEvent(this.selectionForwardBtn, () => { attachClickEvent(this.selectionForwardBtn, () => {
const obj: {[fromPeerId: PeerId]: number[]} = {}; const obj: {[fromPeerId: PeerId]: number[]} = {};
for(const [fromPeerId, mids] of this.selectedMids) { for(const [fromPeerId, mids] of this.selectedMids) {
obj[fromPeerId] = Array.from(mids); obj[fromPeerId] = Array.from(mids).sort((a, b) => a - b);
} }
new PopupForward(obj, () => { new PopupForward(obj, () => {

1
src/components/checkboxField.ts

@ -45,6 +45,7 @@ export default class CheckboxField {
} }
const input = this.input = document.createElement('input'); const input = this.input = document.createElement('input');
input.classList.add('checkbox-field-input');
input.type = 'checkbox'; input.type = 'checkbox';
if(options.name) { if(options.name) {
input.id = 'input-' + options.name; input.id = 'input-' + options.name;

6
src/components/radioForm.ts

@ -4,15 +4,15 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE * https://github.com/morethanwords/tweb/blob/master/LICENSE
*/ */
export default function RadioForm(radios: {container: HTMLElement, input: HTMLInputElement}[], onChange: (value: string) => void) { export default function RadioForm(radios: {container: HTMLElement, input: HTMLInputElement}[], onChange: (value: string, event: Event) => void) {
const form = document.createElement('form'); const form = document.createElement('form');
radios.forEach(r => { radios.forEach(r => {
const {container, input} = r; const {container, input} = r;
form.append(container); form.append(container);
input.addEventListener('change', () => { input.addEventListener('change', (e) => {
if(input.checked) { if(input.checked) {
onChange(input.value); onChange(input.value, e);
} }
}); });
}); });

2
src/config/app.ts

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

27
src/lang.ts

@ -609,6 +609,33 @@ const lang = {
"Contacts.PhoneNumber.NotRegistred": "The person with this phone number is not registered on Telegram yet.", "Contacts.PhoneNumber.NotRegistred": "The person with this phone number is not registered on Telegram yet.",
"Channel.UsernameAboutChannel": "People can share this link with others and can find your channel using Telegram search.", "Channel.UsernameAboutChannel": "People can share this link with others and can find your channel using Telegram search.",
"Channel.UsernameAboutGroup": "People can share this link with others and find your group using Telegram search.", "Channel.UsernameAboutGroup": "People can share this link with others and find your group using Telegram search.",
"Chat.Accessory.Forward": {
"one_value": "Forward Message",
"other_value": "Forward %d Messages"
},
"Chat.Accessory.Forward.You": "You",
"Chat.Accessory.Forward.From": "From",
"Chat.Accessory.Hidden": {
"one_value": "Forward Message (sender's name hidden)",
"other_value": "Forward %d Messages (senders' names hidden)"
},
"Chat.Alert.Forward.Action.Another": "Forward to Another Chat",
"Chat.Alert.Forward.Action.Hide1": {
"one_value": "Hide Sender's Name",
"other_value": "Hide Senders' Names"
},
"Chat.Alert.Forward.Action.Show1": {
"one_value": "Show Sender's Name",
"other_value": "Show Senders' Names"
},
"Chat.Alert.Forward.Action.ShowCaption": {
"one_value": "Show Caption",
"other_value": "Show Captions"
},
"Chat.Alert.Forward.Action.HideCaption": {
"one_value": "Hide Caption",
"other_value": "Hide Captions"
},
"Chat.CopySelectedText": "Copy Selected Text", "Chat.CopySelectedText": "Copy Selected Text",
"Chat.Confirm.Unpin": "Would you like to unpin this message?", "Chat.Confirm.Unpin": "Would you like to unpin this message?",
"Chat.Date.ScheduledFor": "Scheduled for %@", "Chat.Date.ScheduledFor": "Scheduled for %@",

38
src/lib/appManagers/appMessagesManager.ts

@ -445,7 +445,7 @@ export class AppMessagesManager {
scheduleDate: number, scheduleDate: number,
silent: true silent: true
}> = {}) { }> = {}) {
if(typeof(text) !== 'string' || !text.length) { if(!text.trim()) {
return; return;
} }
@ -1538,7 +1538,7 @@ export class AppMessagesManager {
} }
private generateForwardHeader(peerId: PeerId, originalMessage: Message.message) { private generateForwardHeader(peerId: PeerId, originalMessage: Message.message) {
const myId = appUsersManager.getSelf().id; const myId = appUsersManager.getSelf().id.toPeerId();
if(originalMessage.fromId === myId && originalMessage.peerId === myId && !originalMessage.fwd_from) { if(originalMessage.fromId === myId && originalMessage.peerId === myId && !originalMessage.fwd_from) {
return; return;
} }
@ -1891,24 +1891,44 @@ export class AppMessagesManager {
public forwardMessages(peerId: PeerId, fromPeerId: PeerId, mids: number[], options: Partial<{ public forwardMessages(peerId: PeerId, fromPeerId: PeerId, mids: number[], options: Partial<{
withMyScore: true, withMyScore: true,
silent: true, silent: true,
scheduleDate: number scheduleDate: number,
dropAuthor: boolean,
dropCaptions: boolean
}> = {}) { }> = {}) {
peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId; peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId;
mids = mids.slice().sort((a, b) => a - b); mids = mids.slice().sort((a, b) => a - b);
if(options.dropCaptions) {
options.dropAuthor = true;
}
const groups: { const groups: {
[groupId: string]: { [groupId: string]: {
tempId: string, tempId: string,
messages: any[] messages: Message.message[]
} }
} = {}; } = {};
const newMessages = mids.map(mid => { const newMessages = mids.map(mid => {
const originalMessage: Message.message = this.getMessageByPeer(fromPeerId, mid); const originalMessage: Message.message = this.getMessageByPeer(fromPeerId, mid);
const message: Message.message = this.generateOutgoingMessage(peerId, options); const message: Message.message = this.generateOutgoingMessage(peerId, options);
message.fwd_from = this.generateForwardHeader(peerId, originalMessage);
(['entities', 'forwards', 'message', 'media', 'reply_markup', 'views'] as any as Array<keyof MyMessage>).forEach(key => { const keys: Array<keyof Message.message> = [
'entities',
'media',
// 'reply_markup'
];
if(!options.dropAuthor) {
message.fwd_from = this.generateForwardHeader(peerId, originalMessage);
keys.push('views', 'forwards');
}
if(!options.dropCaptions) {
keys.push('message');
}
keys.forEach(key => {
// @ts-ignore // @ts-ignore
message[key] = originalMessage[key]; message[key] = originalMessage[key];
}); });
@ -1956,7 +1976,9 @@ export class AppMessagesManager {
to_peer: appPeersManager.getInputPeerById(peerId), to_peer: appPeersManager.getInputPeerById(peerId),
with_my_score: options.withMyScore, with_my_score: options.withMyScore,
silent: options.silent, silent: options.silent,
schedule_date: options.scheduleDate schedule_date: options.scheduleDate,
drop_author: options.dropAuthor,
drop_media_captions: options.dropCaptions
}, sentRequestOptions).then((updates) => { }, sentRequestOptions).then((updates) => {
this.log('forwardMessages updates:', updates); this.log('forwardMessages updates:', updates);
apiUpdatesManager.processUpdateMessage(updates); apiUpdatesManager.processUpdateMessage(updates);
@ -2738,7 +2760,7 @@ export class AppMessagesManager {
usingFullAlbum = false; usingFullAlbum = false;
} }
if(!usingFullAlbum && !withoutMediaType) { if((!usingFullAlbum && !withoutMediaType) || !text) {
const media = message.media; const media = message.media;
switch(media._) { switch(media._) {
case 'messageMediaPhoto': case 'messageMediaPhoto':

4
src/lib/mediaPlayer.ts

@ -553,7 +553,9 @@ export default class VideoPlayer extends EventListenerBase<{
const buttons: Parameters<typeof ButtonMenu>[0] = [0.25, 0.5, 1, 1.25, 1.5, 2].map((rate) => { const buttons: Parameters<typeof ButtonMenu>[0] = [0.25, 0.5, 1, 1.25, 1.5, 2].map((rate) => {
return { return {
regularText: rate === 1 ? 'Normal' : '' + rate, regularText: rate === 1 ? 'Normal' : '' + rate,
onClick: () => this.video.playbackRate = rate onClick: () => {
this.video.playbackRate = rate;
}
}; };
}); });
const btnMenu = ButtonMenu(buttons); const btnMenu = ButtonMenu(buttons);

9
src/scss/partials/_button.scss

@ -155,6 +155,10 @@
transform-origin: bottom left; transform-origin: bottom left;
} }
&.top-center {
transform-origin: bottom center;
}
&.center-left { &.center-left {
transform-origin: center right; transform-origin: center right;
} }
@ -252,6 +256,11 @@
margin-top: -.125rem; margin-top: -.125rem;
} */ } */
} }
hr {
padding: 0;
margin: .5rem 0;
}
} }
.btn-primary { .btn-primary {

54
src/scss/partials/_chat.scss

@ -454,6 +454,8 @@ $chat-helper-size: 36px;
.reply-wrapper { .reply-wrapper {
height: 0 !important; height: 0 !important;
opacity: 0;
pointer-events: none;
} }
.btn-send { .btn-send {
@ -988,15 +990,61 @@ $chat-helper-size: 36px;
order: 0; order: 0;
pointer-events: none; pointer-events: none;
display: none; // display: none;
} }
&-cancel { &-cancel {
// order: 2; order: 2;
order: 0; // order: 0;
} }
&-subtitle {
color: var(--secondary-text-color) !important;
}
.peer-title {
font-weight: 400;
}
} }
.btn-menu {
top: auto;
bottom: calc(100% + 1.0625rem);
left: 3.125rem;
transform: scale(1) !important;
&-item {
padding-right: 1.5rem;
&-text {
order: 1;
}
.checkbox {
&-field {
--size: 1.5rem;
order: 0;
margin: 0 2rem 0 0;
}
&-box {
&-border,
&-background {
display: none;
}
&-check use {
stroke: var(--primary-color);
}
}
}
}
@include respond-to(handhelds) {
left: calc(var(--padding-horizontal) * -1);
}
}
/* span.emoji { /* span.emoji {
margin: 0 .125rem; margin: 0 .125rem;
// font-size: .8rem; // font-size: .8rem;

1
src/scss/partials/_chatBubble.scss

@ -1620,6 +1620,7 @@ $bubble-beside-button-width: 38px;
align-items: center; align-items: center;
user-select: none; user-select: none;
height: 1.125rem; height: 1.125rem;
line-height: 1;
&.can-autoplay:after { &.can-autoplay:after {
content: $tgico-nosound; content: $tgico-nosound;

3
src/scss/partials/_checkbox.scss

@ -87,6 +87,7 @@
stroke-dasharray: 24.19, 24.19; stroke-dasharray: 24.19, 24.19;
stroke-dashoffset: 0; stroke-dashoffset: 0;
transition: stroke-dasharray .1s .15s ease-in-out, visibility 0s .15s; transition: stroke-dasharray .1s .15s ease-in-out, visibility 0s .15s;
visibility: visible; // fix blinking on parent's transform
@include animation-level(0) { @include animation-level(0) {
transition: none !important; transition: none !important;
@ -262,7 +263,7 @@
position: absolute; position: absolute;
} }
.checkbox-field [type="checkbox"] { .checkbox-field .checkbox-field-input {
&:not(:checked) + .checkbox-box { &:not(:checked) + .checkbox-box {
.checkbox-box-check { .checkbox-box-check {
use { use {

5
src/scss/partials/_poll.scss

@ -292,7 +292,10 @@ poll-element {
font-size: 20px; font-size: 20px;
line-height: 16px; line-height: 16px;
animation: none; animation: none;
transition: opacity .2s ease;
@include animation-level(2) {
transition: opacity .2s ease;
}
} }
} }

Loading…
Cancel
Save