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

60
src/components/chat/bubbles.ts

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

310
src/components/chat/input.ts

@ -70,7 +70,7 @@ import ReplyKeyboard from './replyKeyboard'; @@ -70,7 +70,7 @@ import ReplyKeyboard from './replyKeyboard';
import InlineHelper from './inlineHelper';
import debounce from '../../helpers/schedulers/debounce';
import noop from '../../helpers/noop';
import { putPreloader } from '../misc';
import { openBtnMenu, putPreloader } from '../misc';
import SetTransition from '../singleTransition';
import PeerTitle from '../peerTitle';
import { fastRaf } from '../../helpers/schedulers';
@ -84,6 +84,10 @@ import { NULL_PEER_ID } from '../../lib/mtproto/mtproto_config'; @@ -84,6 +84,10 @@ import { NULL_PEER_ID } from '../../lib/mtproto/mtproto_config';
import setCaretAt from '../../helpers/dom/setCaretAt';
import getKeyFromEvent from '../../helpers/dom/getKeyFromEvent';
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 POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@ -123,7 +127,17 @@ export default class ChatInput { @@ -123,7 +127,17 @@ export default class ChatInput {
iconBtn: HTMLButtonElement
} = {} 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 willSendWebPage: WebPage = null;
@ -313,14 +327,119 @@ export default class ChatInput { @@ -313,14 +327,119 @@ export default class ChatInput {
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 = {
container: forwardBtnMenu
} as any;
forwardBtnMenu.append(forwardElements.changePeer.element);
if(!IS_TOUCH_SUPPORTED) {
const forwardHover = this.forwardHover = new DropdownHover({
element: forwardBtnMenu
});
}
forwardElements.modifyArgs = forwardButtons.slice(0, -1);
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.classList.add('new-message-wrapper');
@ -467,7 +586,7 @@ export default class ChatInput { @@ -467,7 +586,7 @@ export default class ChatInput {
openSide: 'top-left',
onContextElement: this.btnSend,
onOpen: () => {
return !this.isInputEmpty();
return !this.isInputEmpty() || !!Object.keys(this.forwarding).length;
}
});
@ -706,9 +825,15 @@ export default class ChatInput { @@ -706,9 +825,15 @@ export default class ChatInput {
}
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) => {
if(!middleware()) {
return;
}
const minTimestamp = (Date.now() / 1000 | 0) + 10;
if(timestamp <= minTimestamp) {
timestamp = undefined;
@ -718,7 +843,13 @@ export default class ChatInput { @@ -718,7 +843,13 @@ export default class ChatInput {
callback();
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();
};
@ -842,6 +973,12 @@ export default class ChatInput { @@ -842,6 +973,12 @@ export default class ChatInput {
}/* 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) {
this.btnScheduled.classList.add('hide');
const middleware = this.chat.bubbles.getMiddleware();
@ -1668,26 +1805,11 @@ export default class ChatInput { @@ -1668,26 +1805,11 @@ export default class ChatInput {
private onHelperClick = (e: Event) => {
cancelEvent(e);
if(!findUpClassName(e.target, 'reply-wrapper')) return;
if(!findUpClassName(e.target, 'reply')) return;
if(this.helperType === 'forward') {
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();
}
});
if(IS_TOUCH_SUPPORTED && !this.forwardElements.container.classList.contains('active')) {
openBtnMenu(this.forwardElements.container);
}
} else if(this.helperType === 'reply') {
this.chat.setMessageId(this.replyToMsgId);
} else if(this.helperType === 'edit') {
@ -1695,6 +1817,27 @@ export default class ChatInput { @@ -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 = '') {
if(document.activeElement === this.messageInput && IS_MOBILE_SAFARI) { // fix first char uppercase
const i = document.createElement('input');
@ -1787,37 +1930,41 @@ export default class ChatInput { @@ -1787,37 +1930,41 @@ export default class ChatInput {
}
public sendMessage(force = false) {
if(this.chat.type === 'scheduled' && !force && !this.editMsgId) {
const {editMsgId, chat} = this;
if(chat.type === 'scheduled' && !force && !editMsgId) {
this.scheduleSending();
return;
}
const {threadId, peerId} = chat;
const {replyToMsgId, noWebPage, sendSilent, scheduleDate} = this;
const {value, entities} = getRichValue(this.messageInputField.input);
//return;
if(this.editMsgId) {
if(editMsgId) {
const message = this.editMessage;
if(!!value.trim() || message.media) {
if(value.trim() || message.media) {
this.appMessagesManager.editMessage(message, value, {
entities,
noWebPage: this.noWebPage
noWebPage: noWebPage
});
this.onMessageSent();
} else {
new PopupDeleteMessages(this.chat.peerId, [this.editMsgId], this.chat.type);
new PopupDeleteMessages(peerId, [editMsgId], chat.type);
return;
}
} else {
this.appMessagesManager.sendText(this.chat.peerId, value, {
} else if(value.trim()) {
this.appMessagesManager.sendText(peerId, value, {
entities,
replyToMsgId: this.replyToMsgId,
threadId: this.chat.threadId,
noWebPage: this.noWebPage,
replyToMsgId: replyToMsgId,
threadId: threadId,
noWebPage: noWebPage,
webPage: this.getWebPagePromise ? undefined : this.willSendWebPage,
scheduleDate: this.scheduleDate,
silent: this.sendSilent,
scheduleDate: scheduleDate,
silent: sendSilent,
clearDraft: true
});
@ -1828,14 +1975,13 @@ export default class ChatInput { @@ -1828,14 +1975,13 @@ export default class ChatInput {
// * wait for sendText set messageId for invokeAfterMsg
if(this.forwarding) {
const forwarding = copy(this.forwarding);
const peerId = this.chat.peerId;
const silent = this.sendSilent;
const scheduleDate = this.scheduleDate;
setTimeout(() => {
for(const fromPeerId in forwarding) {
this.appMessagesManager.forwardMessages(peerId, fromPeerId.toPeerId(), forwarding[fromPeerId], {
silent,
scheduleDate: scheduleDate
silent: sendSilent,
scheduleDate: scheduleDate,
dropAuthor: this.forwardElements.hideSender.checkboxField.checked,
dropCaptions: this.forwardElements.hideCaption.checkboxField.checked
});
}
@ -1883,6 +2029,12 @@ export default class ChatInput { @@ -1883,6 +2029,12 @@ export default class ChatInput {
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) {
if(this.chat.type === 'scheduled' && !force) {
this.scheduleSending(() => this.sendSomething(callback, true));
@ -1915,7 +2067,7 @@ export default class ChatInput { @@ -1915,7 +2067,7 @@ export default class ChatInput {
//const peerTitles: string[]
const fromPeerIds = Object.keys(fromPeerIdsMids).map(fromPeerId => fromPeerId.toPeerId());
const smth: Set<string> = new Set();
let length = 0;
let length = 0, messagesWithCaptionsLength = 0;
fromPeerIds.forEach(fromPeerId => {
const mids = fromPeerIdsMids[fromPeerId];
@ -1926,6 +2078,10 @@ export default class ChatInput { @@ -1926,6 +2078,10 @@ export default class ChatInput {
} else {
smth.add('P' + message.fromId);
}
if(message.media && message.message) {
++messagesWithCaptionsLength;
}
});
length += mids.length;
@ -1935,19 +2091,25 @@ export default class ChatInput { @@ -1935,19 +2091,25 @@ export default class ChatInput {
const peerTitles = [...smth].map(smth => {
const type = smth[0];
smth = smth.slice(1);
return type === 'P' ?
new PeerTitle({peerId: smth.toPeerId(), dialog: false, onlyFirstName}).element :
(onlyFirstName ? smth.split(' ')[0] : smth);
if(type === 'P') {
const peerId = smth.toPeerId();
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) {
title.append(...join(peerTitles, false));
senderTitles.append(...join(peerTitles, false));
} 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) {
const fromPeerId = fromPeerIds[0];
const mids = fromPeerIdsMids[fromPeerId];
@ -1961,13 +2123,45 @@ export default class ChatInput { @@ -1961,13 +2123,45 @@ export default class ChatInput {
}
}
}
const subtitleFragment = document.createDocumentFragment();
const delimiter = ': ';
if(usingFullAlbum || length === 1) {
const mids = fromPeerIdsMids[fromPeerIds[0]];
const replyFragment = this.appMessagesManager.wrapMessageForReply(firstMessage, undefined, mids);
this.setTopInfo('forward', f, title, replyFragment);
subtitleFragment.append(
senderTitles,
delimiter,
replyFragment
);
} 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;
@ -2114,6 +2308,8 @@ export default class ChatInput { @@ -2114,6 +2308,8 @@ export default class ChatInput {
setTimeout(() => {
this.updateSendBtn();
}, 0);
return newReply;
}
// public saveScroll() {

2
src/components/chat/replyContainer.ts

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

4
src/components/chat/selection.ts

@ -622,7 +622,7 @@ export class SearchSelection extends AppSelection { @@ -622,7 +622,7 @@ export class SearchSelection extends AppSelection {
attachClickEvent(this.selectionForwardBtn, () => {
const obj: {[fromPeerId: PeerId]: number[]} = {};
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, () => {
@ -897,7 +897,7 @@ export default class ChatSelection extends AppSelection { @@ -897,7 +897,7 @@ export default class ChatSelection extends AppSelection {
attachClickEvent(this.selectionForwardBtn, () => {
const obj: {[fromPeerId: PeerId]: number[]} = {};
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, () => {

1
src/components/checkboxField.ts

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

6
src/components/radioForm.ts

@ -4,15 +4,15 @@ @@ -4,15 +4,15 @@
* 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');
radios.forEach(r => {
const {container, input} = r;
form.append(container);
input.addEventListener('change', () => {
input.addEventListener('change', (e) => {
if(input.checked) {
onChange(input.value);
onChange(input.value, e);
}
});
});

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.3.6',
langPackVersion: '0.3.7',
langPack: 'macos',
langPackCode: 'en',
domains: [MAIN_DOMAIN] as string[],

27
src/lang.ts

@ -609,6 +609,33 @@ const lang = { @@ -609,6 +609,33 @@ const lang = {
"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.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.Confirm.Unpin": "Would you like to unpin this message?",
"Chat.Date.ScheduledFor": "Scheduled for %@",

38
src/lib/appManagers/appMessagesManager.ts

@ -445,7 +445,7 @@ export class AppMessagesManager { @@ -445,7 +445,7 @@ export class AppMessagesManager {
scheduleDate: number,
silent: true
}> = {}) {
if(typeof(text) !== 'string' || !text.length) {
if(!text.trim()) {
return;
}
@ -1538,7 +1538,7 @@ export class AppMessagesManager { @@ -1538,7 +1538,7 @@ export class AppMessagesManager {
}
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) {
return;
}
@ -1891,24 +1891,44 @@ export class AppMessagesManager { @@ -1891,24 +1891,44 @@ export class AppMessagesManager {
public forwardMessages(peerId: PeerId, fromPeerId: PeerId, mids: number[], options: Partial<{
withMyScore: true,
silent: true,
scheduleDate: number
scheduleDate: number,
dropAuthor: boolean,
dropCaptions: boolean
}> = {}) {
peerId = appPeersManager.getPeerMigratedTo(peerId) || peerId;
mids = mids.slice().sort((a, b) => a - b);
if(options.dropCaptions) {
options.dropAuthor = true;
}
const groups: {
[groupId: string]: {
tempId: string,
messages: any[]
messages: Message.message[]
}
} = {};
const newMessages = mids.map(mid => {
const originalMessage: Message.message = this.getMessageByPeer(fromPeerId, mid);
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
message[key] = originalMessage[key];
});
@ -1956,7 +1976,9 @@ export class AppMessagesManager { @@ -1956,7 +1976,9 @@ export class AppMessagesManager {
to_peer: appPeersManager.getInputPeerById(peerId),
with_my_score: options.withMyScore,
silent: options.silent,
schedule_date: options.scheduleDate
schedule_date: options.scheduleDate,
drop_author: options.dropAuthor,
drop_media_captions: options.dropCaptions
}, sentRequestOptions).then((updates) => {
this.log('forwardMessages updates:', updates);
apiUpdatesManager.processUpdateMessage(updates);
@ -2738,7 +2760,7 @@ export class AppMessagesManager { @@ -2738,7 +2760,7 @@ export class AppMessagesManager {
usingFullAlbum = false;
}
if(!usingFullAlbum && !withoutMediaType) {
if((!usingFullAlbum && !withoutMediaType) || !text) {
const media = message.media;
switch(media._) {
case 'messageMediaPhoto':

4
src/lib/mediaPlayer.ts

@ -553,7 +553,9 @@ export default class VideoPlayer extends EventListenerBase<{ @@ -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) => {
return {
regularText: rate === 1 ? 'Normal' : '' + rate,
onClick: () => this.video.playbackRate = rate
onClick: () => {
this.video.playbackRate = rate;
}
};
});
const btnMenu = ButtonMenu(buttons);

9
src/scss/partials/_button.scss

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

54
src/scss/partials/_chat.scss

@ -454,6 +454,8 @@ $chat-helper-size: 36px; @@ -454,6 +454,8 @@ $chat-helper-size: 36px;
.reply-wrapper {
height: 0 !important;
opacity: 0;
pointer-events: none;
}
.btn-send {
@ -988,15 +990,61 @@ $chat-helper-size: 36px; @@ -988,15 +990,61 @@ $chat-helper-size: 36px;
order: 0;
pointer-events: none;
display: none;
// display: none;
}
&-cancel {
// order: 2;
order: 0;
order: 2;
// 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 {
margin: 0 .125rem;
// font-size: .8rem;

1
src/scss/partials/_chatBubble.scss

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

3
src/scss/partials/_checkbox.scss

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

5
src/scss/partials/_poll.scss

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

Loading…
Cancel
Save