diff --git a/src/components/appSelectPeers.ts b/src/components/appSelectPeers.ts index 416023882e..8705ce7990 100644 --- a/src/components/appSelectPeers.ts +++ b/src/components/appSelectPeers.ts @@ -11,7 +11,7 @@ import rootScope from '../lib/rootScope'; import Scrollable from './scrollable'; import {FocusDirection} from '../helpers/fastSmoothScroll'; import CheckboxField from './checkboxField'; -import {i18n, LangPackKey, _i18n} from '../lib/langPack'; +import {_i18n, i18n, LangPackKey} from '../lib/langPack'; import findUpAttribute from '../helpers/dom/findUpAttribute'; import findUpClassName from '../helpers/dom/findUpClassName'; import PeerTitle from './peerTitle'; @@ -38,12 +38,112 @@ import getDialogIndex from '../lib/appManagers/utils/dialogs/getDialogIndex'; import {generateDelimiter} from './generateDelimiter'; import SettingSection from './settingSection'; import liteMode from '../helpers/liteMode'; +import {ButtonMenuItemOptions, ButtonMenuSync} from "./buttonMenu"; +import ListenerSetter from "../helpers/listenerSetter"; +import {attachContextMenuListener} from "../helpers/dom/attachContextMenuListener"; +import positionMenu from "../helpers/positionMenu"; +import contextMenuController from "../helpers/contextMenuController"; +import {addFullScreenListener, isFullScreen} from "../helpers/dom/fullScreen"; type SelectSearchPeerType = 'contacts' | 'dialogs' | 'channelParticipants'; // TODO: правильная сортировка для addMembers, т.е. для peerType: 'contacts', потому что там идут сначала контакты - потом неконтакты, а должно всё сортироваться по имени +export class AppSelectPeersContextMenu { + private buttons: (ButtonMenuItemOptions & { verify: (peerId: PeerId) => boolean | Promise })[]; + private element: HTMLElement; + private targetPeerId: PeerId; + private managers: AppManagers; + + + constructor(options: { + listenerSetter: ListenerSetter, + onContextElement: HTMLElement, + toggleMultiSelectState: () => void, + selectContact: (key: PeerId | string) => void + }) { + const {listenerSetter} = options; + this.buttons = [{ + icon: 'select', + text: 'Message.Context.Select', + verify: () => true, + onClick: (e) => { + cancelEvent(e); + options.toggleMultiSelectState(); + options.selectContact(this.targetPeerId); + console.log(this.targetPeerId) + } + }]; + this.element = ButtonMenuSync({buttons: this.buttons, listenerSetter}); + this.element.classList.add('select-peers-menu', 'night'); + + attachContextMenuListener({ + element: options.onContextElement, + callback: async(e) => { + + const li = findUpClassName(e.target, 'chatlist-chat'); + if(!li) { + return; + } + + if(this.element.parentElement !== appendTo) { + appendTo.append(this.element); + } + + cancelEvent(e); + + const peerId = this.targetPeerId = li.dataset.peerId.toPeerId(); + + await filterAsync(this.buttons, async(button) => { + const good = await button.verify(peerId); + button.element.classList.toggle('hide', !good); + return good; + }); + + positionMenu((e as TouchEvent).touches ? (e as TouchEvent).touches[0] : e as MouseEvent, this.element, 'right'); + contextMenuController.openBtnMenu(this.element); + }, + listenerSetter + }); + + let appendTo: HTMLElement = document.body; + addFullScreenListener(document.body, () => { + const isFull = isFullScreen(); + appendTo = document.body; + + if(!isFull) { + contextMenuController.close(); + } + }, listenerSetter); + } + +} + +export interface ISelectPeers { + appendTo: AppSelectPeers['appendTo'], + onChange?: AppSelectPeers['onChange'], + peerType?: AppSelectPeers['peerType'], + peerId?: AppSelectPeers['peerId'], + onFirstRender?: () => void, + renderResultsFunc?: AppSelectPeers['renderResultsFunc'], + chatRightsActions?: AppSelectPeers['chatRightsActions'], + multiSelect?: AppSelectPeers['multiSelect'], + rippleEnabled?: AppSelectPeers['rippleEnabled'], + avatarSize?: AppSelectPeers['avatarSize'], + placeholder?: AppSelectPeers['placeholder'], + selfPresence?: AppSelectPeers['selfPresence'], + exceptSelf?: AppSelectPeers['exceptSelf'], + filterPeerTypeBy?: AppSelectPeers['filterPeerTypeBy'], + sectionNameLangPackKey?: AppSelectPeers['sectionNameLangPackKey'], + managers: AppSelectPeers['managers'], + design?: AppSelectPeers['design'], + listenerSetter?: ListenerSetter, + toggleableMultiSelect?: boolean, +} + export default class AppSelectPeers { + private contextMenu: AppSelectPeersContextMenu; + private listenerSetter: ListenerSetter; public container = document.createElement('div'); public list = appDialogsManager.createChatList(/* { handheldsSize: 66, @@ -52,8 +152,7 @@ export default class AppSelectPeers { private chatsContainer = document.createElement('div'); public scrollable: Scrollable; private selectedScrollable: Scrollable; - - private selectedContainer: HTMLElement; + public selectedContainer: HTMLElement; public input: HTMLInputElement; // public selected: {[peerId: PeerId]: HTMLElement} = {}; @@ -68,7 +167,7 @@ export default class AppSelectPeers { private query = ''; private cachedContacts: PeerId[]; - private loadedWhat: Partial<{[k in 'dialogs' | 'archived' | 'contacts' | 'channelParticipants']: true}> = {}; + private loadedWhat: Partial<{ [k in 'dialogs' | 'archived' | 'contacts' | 'channelParticipants']: true }> = {}; private renderedPeerIds: Set = new Set(); @@ -83,7 +182,7 @@ export default class AppSelectPeers { private exceptSelf = false; private filterPeerTypeBy: IsPeerType[]; - private tempIds: {[k in keyof AppSelectPeers['loadedWhat']]: number} = {}; + private tempIds: { [k in keyof AppSelectPeers['loadedWhat']]: number } = {}; private peerId: PeerId; private placeholder: LangPackKey; @@ -98,25 +197,11 @@ export default class AppSelectPeers { private design: 'round' | 'square' = 'round'; - constructor(options: { - appendTo: AppSelectPeers['appendTo'], - onChange?: AppSelectPeers['onChange'], - peerType?: AppSelectPeers['peerType'], - peerId?: AppSelectPeers['peerId'], - onFirstRender?: () => void, - renderResultsFunc?: AppSelectPeers['renderResultsFunc'], - chatRightsActions?: AppSelectPeers['chatRightsActions'], - multiSelect?: AppSelectPeers['multiSelect'], - rippleEnabled?: AppSelectPeers['rippleEnabled'], - avatarSize?: AppSelectPeers['avatarSize'], - placeholder?: AppSelectPeers['placeholder'], - selfPresence?: AppSelectPeers['selfPresence'], - exceptSelf?: AppSelectPeers['exceptSelf'], - filterPeerTypeBy?: AppSelectPeers['filterPeerTypeBy'], - sectionNameLangPackKey?: AppSelectPeers['sectionNameLangPackKey'], - managers: AppSelectPeers['managers'], - design?: AppSelectPeers['design'] - }) { + public toggleableMultiSelect: boolean = false; + public toggleableMultiSelectState: boolean = false; + + + constructor(options: ISelectPeers) { safeAssign(this, options); this.container.classList.add('selector', 'selector-' + this.design); @@ -195,7 +280,6 @@ export default class AppSelectPeers { simulateClickEvent(li); } }); - section.content.append(topContainer); this.container.append(section.container/* , delimiter */); } @@ -221,7 +305,7 @@ export default class AppSelectPeers { let key: PeerId | string = target.dataset.peerId; key = key.isPeerId() ? key.toPeerId() : key; - if(!this.multiSelect) { + if(!this.multiSelect || (this.multiSelect && this.toggleableMultiSelect && !this.toggleableMultiSelectState)) { this.add(key); return; } @@ -260,6 +344,10 @@ export default class AppSelectPeers { }, 0); } + private toggleMultiSelectState = () => { + this.toggleableMultiSelectState = true; + this.container.querySelectorAll('.checkbox-field').forEach(node => node.classList.remove('hide')); + } private onInput = () => { const value = this.input.value; if(this.query !== value) { @@ -553,7 +641,7 @@ export default class AppSelectPeers { } private getMoreSomething(peerType: SelectSearchPeerType) { - const map: {[type in SelectSearchPeerType]: () => Promise} = { + const map: { [type in SelectSearchPeerType]: () => Promise } = { dialogs: this.getMoreDialogs, contacts: this.getMoreContacts, channelParticipants: this.getMoreChannelParticipants @@ -565,7 +653,22 @@ export default class AppSelectPeers { private async renderResults(peerIds: PeerId[]) { // console.log('will renderResults:', peerIds); - + if(this.toggleableMultiSelect) { + const {listenerSetter} = this; + this.contextMenu = new AppSelectPeersContextMenu({ + onContextElement: this.list, + listenerSetter, + toggleMultiSelectState: () => { + this.toggleMultiSelectState(); + }, + selectContact: (peerId) => { + window.requestAnimationFrame(() => { + const li = this.chatsContainer.querySelector('[data-peer-id="' + peerId + '"]') as HTMLElement; + simulateClickEvent(li); + }) + } + }); + } // оставим только неконтакты с диалогов if(!this.peerType.includes('dialogs') && this.loadedWhat.contacts) { peerIds = await filterAsync(peerIds, (peerId) => { @@ -582,8 +685,12 @@ export default class AppSelectPeers { }); if(this.multiSelect) { + const selected = this.selected.has(peerId); - const checkboxField = new CheckboxField(); + const checkboxField = new CheckboxField({round: true}); + if(!this.toggleableMultiSelectState) { + checkboxField.hide(); + } if(selected) { // dom.listEl.classList.add('active'); @@ -610,7 +717,7 @@ export default class AppSelectPeers { // console.trace('add'); this.selected.add(key); - if(!this.multiSelect) { + if(!this.multiSelect || (this.multiSelect && this.toggleableMultiSelect && !this.toggleableMultiSelectState)) { this.onChange(this.selected.size); return; } @@ -641,7 +748,7 @@ export default class AppSelectPeers { } if(title) { - if(typeof(title) === 'string') { + if(typeof (title) === 'string') { div.innerHTML = title; } else { replaceContent(div, title); @@ -650,8 +757,12 @@ export default class AppSelectPeers { } div.insertAdjacentElement('afterbegin', avatarEl); + try { + this.selectedContainer.insertBefore(div, this.input); + } catch(e) { + this.selectedContainer.append(div); + } - this.selectedContainer.insertBefore(div, this.input); // this.selectedScrollable.scrollTop = this.selectedScrollable.scrollHeight; this.onChange?.(this.selected.size); diff --git a/src/components/checkboxField.ts b/src/components/checkboxField.ts index bcc1439e99..aeeccef643 100644 --- a/src/components/checkboxField.ts +++ b/src/components/checkboxField.ts @@ -6,7 +6,7 @@ import type ListenerSetter from '../helpers/listenerSetter'; import ripple from './ripple'; -import {LangPackKey, _i18n} from '../lib/langPack'; +import {_i18n, LangPackKey} from '../lib/langPack'; import getDeepProperty from '../helpers/object/getDeepProperty'; import rootScope from '../lib/rootScope'; import apiManagerProxy from '../lib/mtproto/mtprotoworker'; @@ -46,6 +46,7 @@ export default class CheckboxField { label.classList.add('checkbox-field-round'); } + if(options.disabled) { this.toggleDisability(true); } @@ -179,6 +180,14 @@ export default class CheckboxField { this.input.checked = checked; } + public show() { + this.label.classList.remove('hide'); + } + + public hide() { + this.label.classList.add('hide'); + } + public isDisabled() { return this.label.classList.contains('checkbox-disabled'); } diff --git a/src/components/emoticonsDropdown/index.ts b/src/components/emoticonsDropdown/index.ts index 9ed22ee78e..2f5eaf8394 100644 --- a/src/components/emoticonsDropdown/index.ts +++ b/src/components/emoticonsDropdown/index.ts @@ -13,7 +13,7 @@ import LazyLoadQueue from '../lazyLoadQueue'; import Scrollable, {ScrollableX} from '../scrollable'; import appSidebarRight from '../sidebarRight'; import StickyIntersector from '../stickyIntersector'; -import EmojiTab from './tabs/emoji'; +import EmojiTab, {getEmojiFromElement} from './tabs/emoji'; import GifsTab from './tabs/gifs'; import StickersTab, {EmoticonsTabC, StickersTabCategory} from './tabs/stickers'; import {MOUNT_CLASS_TO} from '../../config/debug'; @@ -40,9 +40,11 @@ import ListenerSetter from '../../helpers/listenerSetter'; import {ChatRights} from '../../lib/appManagers/appChatsManager'; import {toastNew} from '../toast'; import {POSTING_NOT_ALLOWED_MAP} from '../chat/input'; +import safeAssign from "../../helpers/object/safeAssign"; export const EMOTICONSSTICKERGROUP: AnimationItemGroup = 'emoticons-dropdown'; + export interface EmoticonsTab { content: HTMLElement; scrollable: Scrollable; @@ -55,24 +57,45 @@ export interface EmoticonsTab { onClosed?: () => void; } + const easing = BezierEasing(0.42, 0.0, 0.58, 1.0); const scrollOptions: Partial = { forceDuration: 200, transitionFunction: easing }; +const renderEmojiDropdownElement = (): HTMLDivElement => { + const div = document.createElement('div'); + div.innerHTML = + ``.trim(); + return div.firstElementChild as HTMLDivElement; +} export class EmoticonsDropdown extends DropdownHover { public static lazyLoadQueue = new LazyLoadQueue(1); - private emojiTab: EmojiTab; - private stickersTab: StickersTab; - private gifsTab: GifsTab; private container: HTMLElement; private tabsEl: HTMLElement; private tabId = -1; - private tabs: {[id: number]: EmoticonsTab}; + private tabs: { [id: number]: EmoticonsTab }; private searchButton: HTMLElement; private deleteBtn: HTMLElement; @@ -80,15 +103,21 @@ export class EmoticonsDropdown extends DropdownHover { private selectTab: ReturnType; private savedRange: Range; + private tabsToRender: (EmojiTab | StickersTab | GifsTab)[] = []; private managers: AppManagers; - - private rights: {[action in ChatRights]?: boolean}; - - constructor() { + private rights: { [action in ChatRights]?: boolean }; + + constructor(options?: { + customParentElement?: HTMLElement, + customAnchorElement?: HTMLElement, + tabsToRender?: (EmojiTab | StickersTab | GifsTab)[], + customOnSelect?: (emoji: { element: HTMLElement } & ReturnType) => void, + }) { super({ - element: document.getElementById('emoji-dropdown') as HTMLDivElement, - ignoreOutClickClassName: 'input-message-input' + element: renderEmojiDropdownElement(), + ignoreOutClickClassName: 'input-message-input', }); + safeAssign(this, options); this.rights = { send_gifs: undefined, @@ -103,7 +132,16 @@ export class EmoticonsDropdown extends DropdownHover { } } - if(this.element.parentElement !== appImManager.chat.input.chatInput) { + if(options?.customAnchorElement) { + const anchorRect = options.customAnchorElement.getBoundingClientRect(); + const offset = 64; + this.element.style.left = anchorRect.left + 'px' as string; + this.element.style.bottom = anchorRect.top + offset + 'px' as string; + } + + if(options?.customParentElement) { + options.customParentElement.append(this.element); + } else if(this.element.parentElement !== appImManager.chat.input.chatInput) { appImManager.chat.input.chatInput.append(this.element); } @@ -158,21 +196,27 @@ export class EmoticonsDropdown extends DropdownHover { return this.tabs[this.tabId]; } + public getTabsFromRenderer(instance: typeof EmojiTab | typeof StickersTab | typeof GifsTab): EmojiTab | StickersTab | GifsTab { + return this.tabsToRender.find(tab => (tab instanceof instance)) + } + public init() { this.managers = rootScope.managers; - this.emojiTab = new EmojiTab({managers: this.managers}); - this.stickersTab = new StickersTab(this.managers); - this.gifsTab = new GifsTab(this.managers); - + if(!this.tabsToRender.length) { + this.tabsToRender = [ + new EmojiTab({managers: this.managers}), + new StickersTab(this.managers), + new GifsTab(this.managers)] + } this.tabs = {}; - [this.emojiTab, this.stickersTab, this.gifsTab].forEach((tab, idx) => { + this.tabsToRender.forEach((tab, idx) => { tab.tabId = idx; this.tabs[idx] = tab; }); - this.container = this.element.querySelector('.emoji-container .tabs-container') as HTMLDivElement; - this.container.prepend(this.emojiTab.container, this.stickersTab.container); + this.container.prepend(...this.tabsToRender.map(tab => !(tab instanceof GifsTab) && tab.container)); this.tabsEl = this.element.querySelector('.emoji-tabs') as HTMLUListElement; + this.selectTab = horizontalMenu(this.tabsEl, this.container, this.onSelectTabClick, () => { const {tab} = this; tab.init?.(); @@ -181,7 +225,7 @@ export class EmoticonsDropdown extends DropdownHover { this.searchButton = this.element.querySelector('.emoji-tabs-search'); this.searchButton.addEventListener('click', () => { - if(this.tabId === this.stickersTab.tabId) { + if(this.tabId === this.getTabsFromRenderer(StickersTab)?.tabId) { if(!appSidebarRight.isTabExists(AppStickersTab)) { appSidebarRight.createTab(AppStickersTab).open(); } @@ -272,13 +316,16 @@ export class EmoticonsDropdown extends DropdownHover { const HIDE_EMOJI_TAB = IS_APPLE_MOBILE && false; - const INIT_TAB_ID = HIDE_EMOJI_TAB ? this.stickersTab.tabId : this.emojiTab.tabId; + const INIT_TAB_ID = HIDE_EMOJI_TAB ? this.getTabsFromRenderer(StickersTab).tabId : this.getTabsFromRenderer(EmojiTab).tabId; if(HIDE_EMOJI_TAB) { (this.tabsEl.children[1] as HTMLElement).classList.add('hide'); } simulateClickEvent(this.tabsEl.children[INIT_TAB_ID + 1] as HTMLElement); + if(this.tabsToRender.length <= 1) { + this.tabsEl.classList.add('hide'); + } if(this.tabs[INIT_TAB_ID].init) { this.tabs[INIT_TAB_ID].init(); // onTransitionEnd не вызовется, т.к. это первая открытая вкладка } @@ -332,9 +379,9 @@ export class EmoticonsDropdown extends DropdownHover { return; } - const rights: {[tabId: number]: ChatRights} = { - [this.stickersTab.tabId]: 'send_stickers', - [this.gifsTab.tabId]: 'send_gifs' + const rights: { [tabId: number]: ChatRights } = { + ...(this.getTabsFromRenderer(StickersTab) && {[this.getTabsFromRenderer(StickersTab).tabId]: 'send_stickers'}), + ...(this.getTabsFromRenderer(GifsTab) && {[this.getTabsFromRenderer(GifsTab).tabId]: 'send_gifs'}) }; const action = rights[id]; @@ -346,8 +393,8 @@ export class EmoticonsDropdown extends DropdownHover { animationIntersector.checkAnimations(true, EMOTICONSSTICKERGROUP); this.tabId = id; - this.searchButton.classList.toggle('hide', this.tabId === this.emojiTab.tabId); - this.deleteBtn.classList.toggle('hide', this.tabId !== this.emojiTab.tabId); + this.searchButton.classList.toggle('hide', this.tabId === this.getTabsFromRenderer(EmojiTab)?.tabId); + this.deleteBtn.classList.toggle('hide', this.tabId !== this.getTabsFromRenderer(EmojiTab)?.tabId); }; private checkRights = async() => { @@ -364,8 +411,8 @@ export class EmoticonsDropdown extends DropdownHover { }); const active = this.tabsEl.querySelector('.active'); - if(active && whichChild(active) !== (this.emojiTab.tabId + 1) && (!this.rights['send_stickers'] || !this.rights['send_gifs'])) { - this.selectTab(this.emojiTab.tabId, false); + if(active && whichChild(active) !== (this.getTabsFromRenderer(EmojiTab)?.tabId + 1) && (!this.rights['send_stickers'] || !this.rights['send_gifs'])) { + this.selectTab(this.getTabsFromRenderer(EmojiTab).tabId, false); } }; @@ -473,6 +520,7 @@ export class EmoticonsDropdown extends DropdownHover { }); attachClickEvent(menu, (e) => { + cancelEvent(e); let target = findUpClassName(e.target as HTMLElement, 'menu-horizontal-div-item'); if(!target) { target = findUpClassName(e.target as HTMLElement, 'menu-horizontal-inner'); @@ -527,7 +575,7 @@ export class EmoticonsDropdown extends DropdownHover { return {stickyIntersector, setActive, setActiveStatic}; }; - public static onMediaClick = async(e: {target: EventTarget | Element}, clearDraft = false, silent?: boolean) => { + public static onMediaClick = async(e: { target: EventTarget | Element }, clearDraft = false, silent?: boolean) => { const target = findUpTag(e.target as HTMLElement, 'DIV'); if(!target) return false; diff --git a/src/components/emoticonsDropdown/tabs/emoji.ts b/src/components/emoticonsDropdown/tabs/emoji.ts index 3d3b2c25a0..db57d94234 100644 --- a/src/components/emoticonsDropdown/tabs/emoji.ts +++ b/src/components/emoticonsDropdown/tabs/emoji.ts @@ -14,7 +14,7 @@ import {LangPackKey} from '../../../lib/langPack'; import rootScope from '../../../lib/rootScope'; import {emojiFromCodePoints} from '../../../vendor/emoji'; import {putPreloader} from '../../putPreloader'; -import Scrollable, {ScrollableX} from '../../scrollable'; +import {ScrollableX} from '../../scrollable'; import IS_EMOJI_SUPPORTED from '../../../environment/emojiSupport'; import IS_TOUCH_SUPPORTED from '../../../environment/touchSupport'; import blurActiveElement from '../../../helpers/dom/blurActiveElement'; @@ -30,7 +30,6 @@ import VisibilityIntersector, {OnVisibilityChangeItem} from '../../visibilityInt import mediaSizes from '../../../helpers/mediaSizes'; import wrapStickerSetThumb from '../../wrappers/stickerSetThumb'; import attachStickerViewerListeners from '../../stickerViewer'; -import ListenerSetter from '../../../helpers/listenerSetter'; import {Document, StickerSet} from '../../../layer'; import {CustomEmojiElement, CustomEmojiRendererElement} from '../../../lib/richTextProcessor/wrapRichText'; import findAndSplice from '../../../helpers/array/findAndSplice'; @@ -42,6 +41,7 @@ import type {AppStickersManager} from '../../../lib/appManagers/appStickersManag import liteMode from '../../../helpers/liteMode'; const loadedURLs: Set = new Set(); + export function appendEmoji(emoji: string, container?: HTMLElement, prepend = false, unify = false) { // const emoji = details.unified; // const emoji = (details.unified as string).split('-') @@ -114,7 +114,7 @@ export function appendEmoji(emoji: string, container?: HTMLElement, prepend = fa return spanEmoji; } -export function getEmojiFromElement(element: HTMLElement): {docId?: DocId, emoji: string} { +export function getEmojiFromElement(element: HTMLElement): { docId?: DocId, emoji: string } { const superEmoji = findUpClassName(element, 'super-emoji'); if(!superEmoji) return; @@ -148,6 +148,7 @@ const EMOJI_CATEGORIES: [LangPackKey | '', string][] = [ ]; let sorted: Map<(typeof EMOJI_CATEGORIES)[0], string[]>; + function prepare() { if(sorted) { return sorted; @@ -186,12 +187,13 @@ function prepare() { const EMOJI_ELEMENT_SIZE = makeMediaSize(42, 42); const RECENT_MAX_LENGTH = 32; -type EmojiTabItem = {element: HTMLElement} & ReturnType; -type EmojiTabCategory = StickersTabCategory; +type EmojiTabItem = { element: HTMLElement } & ReturnType; +type EmojiTabCategory = StickersTabCategory; export default class EmojiTab extends EmoticonsTabC { private closeScrollTop: number; private menuInnerScroll: ScrollableX; private isStandalone?: boolean; + private showInnerMenuAnyway?: boolean; private noRegularEmoji?: boolean; private stickerSetId?: Parameters[0]; private onClick: (emoji: EmojiTabItem) => void; @@ -203,7 +205,8 @@ export default class EmojiTab extends EmoticonsTabC { isStandalone?: boolean, noRegularEmoji?: boolean, stickerSetId?: EmojiTab['stickerSetId'], - onClick?: EmojiTab['onClick'] + onClick?: EmojiTab['onClick'], + showInnerMenuAnyway?: boolean }) { super( options.managers, @@ -274,7 +277,8 @@ export default class EmojiTab extends EmoticonsTabC { customEmojis.set(customEmojiElement.docId, new Set([customEmojiElement])); }); - /* const promise = */renderer.add(customEmojis/* , EmoticonsDropdown.lazyLoadQueue */, undefined, true); + /* const promise = */ + renderer.add(customEmojis/* , EmoticonsDropdown.lazyLoadQueue */, undefined, true); // promise.then(() => { // customEmojis.forEach((elements) => { // elements.forEach((element) => { @@ -328,7 +332,7 @@ export default class EmojiTab extends EmoticonsTabC { let innerScrollWrapper: HTMLElement; - if(!this.isStandalone) { + if((this.showInnerMenuAnyway && this.isStandalone) || !this.isStandalone) { const x = this.menuInnerScroll = new ScrollableX(undefined); x.container.classList.add('menu-horizontal-inner-scroll'); @@ -487,6 +491,7 @@ export default class EmojiTab extends EmoticonsTabC { } }; + !this.isStandalone && this.listenerSetter.add(emoticonsDropdown)('opened', () => { toggleRenderers(false); }); diff --git a/src/components/emoticonsDropdown/tabs/gifs.ts b/src/components/emoticonsDropdown/tabs/gifs.ts index bc5cd15053..4e043fa387 100644 --- a/src/components/emoticonsDropdown/tabs/gifs.ts +++ b/src/components/emoticonsDropdown/tabs/gifs.ts @@ -15,7 +15,6 @@ export default class GifsTab implements EmoticonsTab { public content: HTMLElement; public scrollable: Scrollable; public tabId: number; - constructor(private managers: AppManagers) { } diff --git a/src/components/emoticonsDropdown/tabs/stickers.ts b/src/components/emoticonsDropdown/tabs/stickers.ts index f13badda9a..6fb343f158 100644 --- a/src/components/emoticonsDropdown/tabs/stickers.ts +++ b/src/components/emoticonsDropdown/tabs/stickers.ts @@ -4,7 +4,7 @@ * https://github.com/morethanwords/tweb/blob/master/LICENSE */ -import emoticonsDropdown, {EmoticonsDropdown, EMOTICONSSTICKERGROUP, EmoticonsTab} from '..'; +import emoticonsDropdown, { EmoticonsDropdown, EMOTICONSSTICKERGROUP, EmoticonsTab} from '..'; import findUpClassName from '../../../helpers/dom/findUpClassName'; import mediaSizes from '../../../helpers/mediaSizes'; import {Document, MessagesAllStickers, StickerSet} from '../../../layer'; @@ -180,6 +180,7 @@ export class StickersTabCategory> imple type StickersTabItem = {element: HTMLElement, document: Document.document}; export default class StickersTab extends EmoticonsTabC> { private superStickerRenderer: SuperStickerRenderer; - constructor(managers: AppManagers) { super( managers, diff --git a/src/components/inputField.ts b/src/components/inputField.ts index f6b711c72b..b40b194862 100644 --- a/src/components/inputField.ts +++ b/src/components/inputField.ts @@ -17,12 +17,14 @@ import RichInputHandler, {USING_BOMS} from '../helpers/dom/richInputHandler'; import selectElementContents from '../helpers/dom/selectElementContents'; import setInnerHTML, {setDirection} from '../helpers/dom/setInnerHTML'; import {MessageEntity} from '../layer'; -import {i18n, LangPackKey, _i18n} from '../lib/langPack'; +import {_i18n, i18n, LangPackKey} from '../lib/langPack'; import {NULL_PEER_ID} from '../lib/mtproto/mtproto_config'; import mergeEntities from '../lib/richTextProcessor/mergeEntities'; import parseEntities from '../lib/richTextProcessor/parseEntities'; import wrapDraftText from '../lib/richTextProcessor/wrapDraftText'; -import {createCustomFiller, CustomEmojiElement, CustomEmojiRendererElement, insertCustomFillers, renderEmojis} from '../lib/richTextProcessor/wrapRichText'; +import {createCustomFiller, CustomEmojiElement, CustomEmojiRendererElement, insertCustomFillers} from '../lib/richTextProcessor/wrapRichText'; +import {getEmojiFromElement} from "./emoticonsDropdown/tabs/emoji"; +import getEmojiEntityFromEmoji from "../lib/richTextProcessor/getEmojiEntityFromEmoji"; export async function insertRichTextAsHTML(input: HTMLElement, text: string, entities: MessageEntity[], wrappingForPeerId: PeerId) { const loadPromises: Promise[] = []; @@ -218,7 +220,8 @@ let init = () => { richValue.entities = richValue.entities.filter((entity) => entity._ !== 'messageEntityCustomEmoji'); } - /* if(false) */ { // * fix extra new lines appearing from

(can have them from some sources, like macOS Terminal) + /* if(false) */ + { // * fix extra new lines appearing from

(can have them from some sources, like macOS Terminal) const lines = richValue.value.split('\n'); let textLength = 0; for(let lineIndex = 0; lineIndex < lines.length; ++lineIndex) { @@ -494,7 +497,7 @@ export default class InputField { `; - input = this.container.firstElementChild as HTMLElement; + input = this.container.firstElementChild as HTMLInputElement; // input.addEventListener('input', () => checkAndSetRTL(input)); } @@ -657,4 +660,5 @@ export default class InputField { public setError(label?: LangPackKey) { this.setState(InputState.Error, label); } + } diff --git a/src/components/popups/forward.ts b/src/components/popups/forward.ts index 0de5aeef86..9d6fd8f501 100644 --- a/src/components/popups/forward.ts +++ b/src/components/popups/forward.ts @@ -14,9 +14,10 @@ import getMediaFromMessage from '../../lib/appManagers/utils/messages/getMediaFr export default class PopupForward extends PopupPickUser { constructor( - peerIdMids?: {[fromPeerId: PeerId]: number[]}, + peerIdMids?: { [fromPeerId: PeerId]: number[] }, onSelect?: (peerId: PeerId) => Promise | void, - chatRightsAction: ChatRights[] = ['send_plain'] + chatRightsAction: ChatRights[] = ['send_plain'], + onSelectMultiple?: (peerId: PeerId[], message: string) => Promise | void, ) { super({ peerTypes: ['dialogs', 'contacts'], @@ -27,7 +28,6 @@ export default class PopupForward extends PopupPickUser { await res; } } - if(peerId === rootScope.myId) { let count = 0; for(const fromPeerId in peerIdMids) { @@ -35,17 +35,31 @@ export default class PopupForward extends PopupPickUser { count += mids.length; this.managers.appMessagesManager.forwardMessages(peerId, fromPeerId.toPeerId(), mids); } - toastNew({ langPackKey: count > 0 ? 'FwdMessagesToSavedMessages' : 'FwdMessageToSavedMessages' }); - return; } appImManager.setInnerPeer({peerId}); appImManager.chat.input.initMessagesForward(peerIdMids); }, + onSelectMultiple: !peerIdMids && onSelectMultiple ? onSelectMultiple : (peerIds, message: string) => { + peerIds.forEach(async(peerId: PeerId) => { + let count = 0; + for(const fromPeerId in peerIdMids) { + const mids = peerIdMids[fromPeerId]; + count += mids.length; + if(message) { + this.managers.appMessagesManager.sendText(peerId, message); + } + this.managers.appMessagesManager.forwardMessages(peerId, fromPeerId.toPeerId(), mids); + } + return; + }); + appImManager.chat.selection.cancelSelection(false); + }, + placeholder: 'ShareModal.Search.ForwardPlaceholder', chatRightsActions: chatRightsAction, selfPresence: 'ChatYourSelf' diff --git a/src/components/popups/newMedia.ts b/src/components/popups/newMedia.ts index d3ea53f9c6..0112658851 100644 --- a/src/components/popups/newMedia.ts +++ b/src/components/popups/newMedia.ts @@ -16,13 +16,12 @@ import {MyDocument} from '../../lib/appManagers/appDocsManager'; import I18n, {FormatterArguments, i18n, LangPackKey} from '../../lib/langPack'; import calcImageInBox from '../../helpers/calcImageInBox'; import placeCaretAtEnd from '../../helpers/dom/placeCaretAtEnd'; -import {attachClickEvent} from '../../helpers/dom/clickEvent'; import MEDIA_MIME_TYPES_SUPPORTED from '../../environment/mediaMimeTypesSupport'; import getGifDuration from '../../helpers/getGifDuration'; import replaceContent from '../../helpers/dom/replaceContent'; import createVideo from '../../helpers/dom/createVideo'; import prepareAlbum from '../prepareAlbum'; -import {makeMediaSize, MediaSize} from '../../helpers/mediaSize'; +import {makeMediaSize} from '../../helpers/mediaSize'; import {ThumbCache} from '../../lib/storages/thumbs'; import onMediaLoad from '../../helpers/onMediaLoad'; import apiManagerProxy from '../../lib/mtproto/mtprotoworker'; @@ -48,6 +47,7 @@ import rootScope from '../../lib/rootScope'; import shake from '../../helpers/dom/shake'; import AUDIO_MIME_TYPES_SUPPORTED from '../../environment/audioMimeTypeSupport'; import liteMode from '../../helpers/liteMode'; +import {CommentSection} from "./popupCommentSection"; type SendFileParams = SendFileDetails & { file?: File, @@ -83,6 +83,7 @@ export default class PopupNewMedia extends PopupElement { private animationGroup: AnimationItemGroup; private _scrollable: Scrollable; private inputContainer: HTMLDivElement; + private commentSection: CommentSection; constructor( private chat: Chat, @@ -116,7 +117,7 @@ export default class PopupNewMedia extends PopupElement { return peerId.isAnyChat() && !onlyVisible ? rootScope.managers.appChatsManager.hasRights(peerId.toChatId(), action) : true; }); - const out: {[action in ChatRights]?: boolean} = {}; + const out: { [action in ChatRights]?: boolean } = {}; const results = await Promise.all(actionsPromises); actions.forEach((action, idx) => { @@ -142,7 +143,7 @@ export default class PopupNewMedia extends PopupElement { const canSendVideos = canSend.send_videos; const canSendDocs = canSend.send_docs; - attachClickEvent(this.btnConfirm, () => this.send(), {listenerSetter: this.listenerSetter}); + // attachClickEvent(this.btnConfirm, () => this.send(), {listenerSetter: this.listenerSetter}); const btnMenu = await ButtonMenuToggle({ listenerSetter: this.listenerSetter, @@ -221,37 +222,6 @@ export default class PopupNewMedia extends PopupElement { this.mediaContainer.classList.add('popup-photo'); this.scrollable.container.append(this.mediaContainer); - const inputContainer = this.inputContainer = document.createElement('div'); - inputContainer.classList.add('popup-input-container'); - - const c = document.createElement('div'); - c.classList.add('popup-input-inputs', 'input-message-container'); - - this.messageInputField = new InputFieldAnimated({ - placeholder: 'PreviewSender.CaptionPlaceholder', - name: 'message', - withLinebreaks: true, - maxLength: this.captionLengthMax - }); - - this.listenerSetter.add(this.scrollable.container)('scroll', this.onScroll); - this.listenerSetter.add(this.messageInputField.input)('scroll', this.onScroll); - - this.messageInputField.input.classList.replace('input-field-input', 'input-message-input'); - this.messageInputField.inputFake.classList.replace('input-field-input', 'input-message-input'); - - c.append(this.messageInputField.input, this.messageInputField.inputFake); - inputContainer.append(c, this.btnConfirm); - - if(!this.ignoreInputValue) { - this.messageInputField.value = this.wasInputValue = this.chat.input.messageInputField.input.innerHTML; - this.chat.input.messageInputField.value = ''; - } - - this.container.append(inputContainer); - - this.attachFiles(); - this.addEventListener('close', () => { this.files.length = 0; this.willAttach.sendFileDetails.length = 0; @@ -292,11 +262,11 @@ export default class PopupNewMedia extends PopupElement { const sendMenu = new SendContextMenu({ onSilentClick: () => { this.chat.input.sendSilent = true; - this.send(); + this.send(false, this.wasInputValue); }, onScheduleClick: () => { this.chat.input.scheduleSending(() => { - this.send(); + this.send(false, this.wasInputValue); }); }, openSide: 'top-left', @@ -308,7 +278,21 @@ export default class PopupNewMedia extends PopupElement { this.container.append(sendMenu.sendMenu); } + this.commentSection = new CommentSection({ + container: this.container, + onSubmit: (message) => this.send(false, message), + managers: this.managers, + scrollable: this.scrollable + }); + await this.commentSection.construct(); + this.messageInputField = this.commentSection.messageInputField + + if(!this.ignoreInputValue) { + this.wasInputValue = this.chat.input.messageInputField.input.innerHTML; + this.chat.input.messageInputField.value = ''; + } + this.attachFiles(); currentPopup = this; } @@ -448,7 +432,7 @@ export default class PopupNewMedia extends PopupElement { if(good) { const media = this.willAttach.sendFileDetails - .filter((d) => MEDIA_MIME_TYPES_SUPPORTED.has(d.file.type)) + .filter((d) => MEDIA_MIME_TYPES_SUPPORTED.has(d.file.type)) const mediaWithSpoilers = media.filter((d) => d.mediaSpoiler); good = single ? true : media.length > 1; @@ -509,8 +493,8 @@ export default class PopupNewMedia extends PopupElement { } }; - private async send(force = false) { - let caption = this.messageInputField.value; + private async send(force = false, message: string) { + let caption = message; if(caption.length > this.captionLengthMax) { toast(I18n.format('Error.PreviewSender.CaptionTooLong', true)); return; @@ -544,7 +528,7 @@ export default class PopupNewMedia extends PopupElement { } const found = a.find(([verify]) => { - return typeof(verify) === 'function' ? verify() : verify.has(params.file.type); + return typeof (verify) === 'function' ? verify() : verify.has(params.file.type); }); if(found) { @@ -554,7 +538,7 @@ export default class PopupNewMedia extends PopupElement { return (!isMedia && !canSend.send_docs && 'GlobalAttachDocumentsRestricted') || undefined; }); - const key = isBad.find((i) => typeof(i) === 'string') as LangPackKey; + const key = isBad.find((i) => typeof (i) === 'string') as LangPackKey; if(key) { toastNew({ langPackKey: key @@ -574,7 +558,7 @@ export default class PopupNewMedia extends PopupElement { if(this.chat.type === 'scheduled' && !force) { this.chat.input.scheduleSending(() => { - this.send(true); + this.send(true, this.wasInputValue); }); return; @@ -724,7 +708,8 @@ export default class PopupNewMedia extends PopupElement { ...thumb }; }) - ]).then(() => {}); + ]).then(() => { + }); } } } @@ -882,12 +867,12 @@ export default class PopupNewMedia extends PopupElement { args.push(files.length); } else - /* const sum = foundPhotos + foundVideos; - if(sum > 1 && willAttach.group) { - key = 'PreviewSender.SendAlbum'; - const albumsLength = Math.ceil(sum / 10); - args.push(albumsLength); - } else */if(foundPhotos) { + /* const sum = foundPhotos + foundVideos; + if(sum > 1 && willAttach.group) { + key = 'PreviewSender.SendAlbum'; + const albumsLength = Math.ceil(sum / 10); + args.push(albumsLength); + } else */if(foundPhotos) { key = 'PreviewSender.SendPhoto'; args.push(foundPhotos); } else if(foundVideos) { diff --git a/src/components/popups/pickUser.ts b/src/components/popups/pickUser.ts index 91b4ee18d2..fd6cfaae8f 100644 --- a/src/components/popups/pickUser.ts +++ b/src/components/popups/pickUser.ts @@ -7,14 +7,20 @@ import IS_TOUCH_SUPPORTED from '../../environment/touchSupport'; import AppSelectPeers from '../appSelectPeers'; import PopupElement from '.'; -import {LangPackKey, _i18n} from '../../lib/langPack'; +import {LangPackKey} from '../../lib/langPack'; + +import {toastNew} from "../toast"; + +import {CommentSection} from "./popupCommentSection"; export default class PopupPickUser extends PopupElement { protected selector: AppSelectPeers; + private commentSection: CommentSection; constructor(options: { peerTypes: AppSelectPeers['peerType'], onSelect?: (peerId: PeerId) => Promise | void, + onSelectMultiple?: (peerIds: PeerId[], message: string) => Promise | void, placeholder: LangPackKey, chatRightsActions?: AppSelectPeers['chatRightsActions'], peerId?: number, @@ -26,21 +32,55 @@ export default class PopupPickUser extends PopupElement { appendTo: this.body, onChange: async() => { const selected = this.selector.getSelected(); - const peerId = selected[selected.length - 1].toPeerId(); + if(!this.selector.toggleableMultiSelect || (this.selector.toggleableMultiSelect && !this.selector.toggleableMultiSelectState)) { + const peerId = selected[selected.length - 1].toPeerId(); + if(options.onSelect) { + const res = options.onSelect(peerId); + if(res instanceof Promise) { + try { + await res; + } catch(err) { + return; + } + } + } + this.selector = null; + this.hide(); + } else { + if(options.onSelectMultiple) { + const peerIds = selected.map(peer => peer.toPeerId()); + const submitForward = async(message: string) => { + if(peerIds.length) { + const res = options.onSelectMultiple(peerIds, message); + if(res instanceof Promise) { + try { + await res; + } catch(err) { + return; + } + } + this.selector = null; + this.hide(); + } else { + toastNew({langPackKey: 'SelectRecipient'}); + } + } - if(options.onSelect) { - const res = options.onSelect(peerId); - if(res instanceof Promise) { - try { - await res; - } catch(err) { - return; + if(!this.commentSection) { + this.commentSection = new CommentSection({ + container: this.container, + onSubmit: submitForward, + managers: this.managers, + scrollable: this.scrollable + }); + this.commentSection.construct(); + } else { + this.commentSection.onSubmit = submitForward; } } } - this.selector = null; - this.hide(); + }, peerType: options.peerTypes, onFirstRender: () => { @@ -52,20 +92,20 @@ export default class PopupPickUser extends PopupElement { } }, chatRightsActions: options.chatRightsActions, - multiSelect: false, + multiSelect: true, + toggleableMultiSelect: true, rippleEnabled: false, avatarSize: 'abitbigger', peerId: options.peerId, placeholder: options.placeholder, selfPresence: options.selfPresence, - managers: this.managers + managers: this.managers, }); this.scrollable = this.selector.scrollable; this.attachScrollableListeners(); - // this.scrollable = new Scrollable(this.body); - this.title.append(this.selector.input); } } + diff --git a/src/components/popups/popupCommentSection.ts b/src/components/popups/popupCommentSection.ts new file mode 100644 index 0000000000..4f7eb42d54 --- /dev/null +++ b/src/components/popups/popupCommentSection.ts @@ -0,0 +1,141 @@ +import ListenerSetter from "../../helpers/listenerSetter"; +import InputFieldAnimated from "../inputFieldAnimated"; +import Scrollable from "../scrollable"; +import {AppManagers} from "../../lib/appManagers/managers"; +import ButtonIcon from "../buttonIcon"; +import safeAssign from "../../helpers/object/safeAssign"; +import SettingSection from "../settingSection"; +import EmojiTab, {getEmojiFromElement} from "../emoticonsDropdown/tabs/emoji"; +import {MessageEntity} from "../../layer"; +import getEmojiEntityFromEmoji from "../../lib/richTextProcessor/getEmojiEntityFromEmoji"; +import {insertRichTextAsHTML} from "../inputField"; +import {EmoticonsDropdown} from "../emoticonsDropdown"; +import {attachClickEvent} from "../../helpers/dom/clickEvent"; +import cancelEvent from "../../helpers/dom/cancelEvent"; +import IS_TOUCH_SUPPORTED from "../../environment/touchSupport"; +import {i18n, LangPackKey} from "../../lib/langPack"; +import ripple from "../ripple"; +import {IS_MOBILE} from "../../environment/userAgent"; + +export class CommentSection { + + public container: HTMLElement; + public onSubmit: (message: string) => void; + private listenerSetter: ListenerSetter; + private btnToggleEmoticons: HTMLAnchorElement | HTMLButtonElement; + private inputContainer: HTMLDivElement; + public messageInputField: InputFieldAnimated; + private scrollable: Scrollable; + protected managers: AppManagers; + private btnConfirm: HTMLButtonElement; + confirmMessage: LangPackKey + + private createButtonIcon(...args: Parameters) { + const button = ButtonIcon(...args); + return button; + } + + constructor(options: { + container: HTMLElement, + onSubmit: (message: string) => void, + managers: AppManagers, + scrollable: Scrollable, + confirmMessage?: LangPackKey + btnConfirm?: HTMLButtonElement + }) { + if(!options.btnConfirm) { + this.btnConfirm = document.createElement('button'); + this.btnConfirm.classList.add('btn-primary', 'btn-color-primary'); + options.confirmMessage ? this.btnConfirm.append(i18n(options.confirmMessage)) : this.btnConfirm.append(i18n('Modal.Send')); + ripple(this.btnConfirm); + } + safeAssign(this, options); + } + + public construct = async() => { + const section = new SettingSection({}); + section.innerContainer.classList.add('popup-comment-container'); + const div = document.createElement('div'); + div.classList.add('popup-comment'); + + const inputContainer = this.inputContainer = document.createElement('div'); + inputContainer.classList.add('popup-input-container'); + + + this.listenerSetter = new ListenerSetter(); + if(!IS_MOBILE) { + const customOnSelect = (emoji: ReturnType) => { + const getEntityFromEmoji = (emoji: ReturnType) => { + const entity: MessageEntity = emoji.docId ? { + _: 'messageEntityCustomEmoji', + document_id: emoji.docId, + length: emoji.emoji.length, + offset: 0 + } : getEmojiEntityFromEmoji(emoji.emoji); + return entity; + }; + insertRichTextAsHTML(this.messageInputField.input, emoji.emoji, [getEntityFromEmoji(emoji)], null) + } + this.btnToggleEmoticons = this.createButtonIcon('none toggle-emoticons', {noRipple: true}); + const emoticonsDropdown = new EmoticonsDropdown({ + customAnchorElement: this.container, + customParentElement: this.container.parentElement, tabsToRender: [new EmojiTab({ + managers: this.managers, + onClick: customOnSelect, + isStandalone: true, + showInnerMenuAnyway: true + })] + }); + this.container.append(emoticonsDropdown.getElement()); + emoticonsDropdown.attachButtonListener(this.btnToggleEmoticons, this.listenerSetter); + this.listenerSetter.add(emoticonsDropdown)('open', this.onEmoticonsOpen); + this.listenerSetter.add(emoticonsDropdown)('close', this.onEmoticonsClose); + div.append(this.btnToggleEmoticons); + } + div.append(inputContainer); + section.innerContainer.append(div); + this.container.append(section.innerContainer); + attachClickEvent(this.btnConfirm, (e) => { + cancelEvent(e); + this.onSubmit(this.messageInputField.value); + }) + await this.createMessageContainer(section.innerContainer); + } + + private createMessageContainer = async(inputContainer: HTMLElement) => { + const c = document.createElement('div'); + c.classList.add('popup-input-inputs', 'input-message-container'); + const captionMaxLength = await this.managers.apiManager.getLimit('caption'); + this.messageInputField = new InputFieldAnimated({ + placeholder: 'PreviewSender.CaptionPlaceholder', + name: 'message', + withLinebreaks: true, + maxLength: captionMaxLength + }); + + this.listenerSetter.add(this.scrollable.container)('scroll', this.onScroll); + this.listenerSetter.add(this.messageInputField.input)('scroll', this.onScroll); + + this.messageInputField.input.classList.replace('input-field-input', 'input-message-input'); + this.messageInputField.inputFake.classList.replace('input-field-input', 'input-message-input'); + c.append(this.messageInputField.input, this.messageInputField.inputFake); + inputContainer.append(c, this.btnConfirm); + } + private onEmoticonsOpen = () => { + const toggleClass = IS_TOUCH_SUPPORTED ? 'flip-icon' : 'active'; + this.btnToggleEmoticons.classList.toggle(toggleClass, true); + }; + + private onEmoticonsClose = () => { + const toggleClass = IS_TOUCH_SUPPORTED ? 'flip-icon' : 'active'; + this.btnToggleEmoticons.classList.toggle(toggleClass, false); + }; + private onScroll = () => { + const {input} = this.messageInputField; + this.scrollable.onAdditionalScroll(); + if(input.scrollTop > 0 && input.scrollHeight > 130) { + this.scrollable.container.classList.remove('scrolled-bottom'); + } + }; +} + diff --git a/src/index.hbs b/src/index.hbs index 759544a90d..9a6a77c195 100644 --- a/src/index.hbs +++ b/src/index.hbs @@ -150,23 +150,5 @@

- diff --git a/src/lang.ts b/src/lang.ts index 497eb274cd..404b6618af 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -907,6 +907,7 @@ const lang = { 'LimitReachedFoldersLocked': 'You have reached the limit of **%1$d** folders for this account. We are working to let you increase this limit in the future.', 'FwdMessageToSavedMessages': 'Message forwarded to **Saved Messages**.', 'FwdMessagesToSavedMessages': 'Messages forwarded to **Saved Messages**.', + 'SelectRecipient': 'Select recepient', 'ColorTheme': 'Color theme', 'SendAsFile': 'Send as file', 'SendAsFiles': 'Send as files', diff --git a/src/scss/partials/_button.scss b/src/scss/partials/_button.scss index a8c086b31b..d863816fad 100644 --- a/src/scss/partials/_button.scss +++ b/src/scss/partials/_button.scss @@ -113,7 +113,13 @@ $btn-menu-z-index: 4; transition: opacity var(--btn-menu-transition), transform var(--btn-menu-transition), visibility var(--btn-menu-transition); font-size: 1rem; backdrop-filter: var(--menu-backdrop-filter); - min-width: 11.25rem; + // На мобильной вьюхе кропка огромная, не уверен для чего минимальная ширина была такая большая, но предлагаю сделать так, можно откатить, если так и было задумано + min-width: min-content; + max-width: max-content; + @include respond-to(medium-screens) { + min-width: 11.25rem; + max-width: unset; + } // max-width: 16.25rem; &-old, diff --git a/src/scss/partials/popups/_forward.scss b/src/scss/partials/popups/_forward.scss index cedb23c6f7..b9ff28ed35 100644 --- a/src/scss/partials/popups/_forward.scss +++ b/src/scss/partials/popups/_forward.scss @@ -40,17 +40,19 @@ overflow: hidden; display: flex; width: 100%; - flex-direction: row; flex: 1 1 auto; } + .chatlist-container { + flex-direction: row; + } + .selector { &-search-input { font-size: var(--font-size-20); line-height: 1; padding: .5rem 1.5rem; width: 100%; - height: 100%; @include respond-to(handhelds) { padding-left: 1.1875rem; @@ -59,6 +61,9 @@ .chatlist { margin-top: 0 !important; + .user-title{ + padding-right: 1rem; + } } } @@ -70,4 +75,21 @@ padding-top: 0 !important; margin-bottom: 0 !important; } + + .selector-user { + max-width: calc(50% - .75rem); + } + + + @keyframes appear { + 0% { + height: 0; + opacity: 0; + } + + to { + height: auto; + opacity: 1; + } + } } diff --git a/src/scss/partials/popups/_mediaAttacher.scss b/src/scss/partials/popups/_mediaAttacher.scss index 1e9e13a77d..4c30b09c14 100644 --- a/src/scss/partials/popups/_mediaAttacher.scss +++ b/src/scss/partials/popups/_mediaAttacher.scss @@ -18,7 +18,7 @@ //max-height: unquote('min(640px, 100%)'); max-height: 100%; - img, + img, video { border-radius: inherit; } @@ -32,7 +32,7 @@ margin-bottom: 9px; padding: 12px 20px 15px; position: relative; - + .btn-primary { width: auto; height: 2.25rem; @@ -43,11 +43,11 @@ line-height: 2.25rem; } } - + &-close { margin: -1px 0 0 -4px; } - + &-photo { max-width: 100%; overflow: hidden; @@ -59,9 +59,6 @@ } } - .scrollable { - position: relative; - } .input-field { width: 100%; @@ -134,6 +131,7 @@ position: relative; .scrollable { + position: relative; padding: 0 .5rem; } } @@ -155,40 +153,6 @@ height: 2.5rem; line-height: 2.5rem; text-transform: uppercase; - margin-bottom: .5rem; - } - - .popup-input-container { - --height: 3.5rem; - --max-height: 8.375rem; - display: flex; - align-items: flex-end; - justify-content: space-between; - padding: 0 .5rem; - min-height: var(--height); - max-height: var(--max-height); - position: relative; - flex: 0 0 auto; - overflow: hidden; - - &:before { - content: " "; - position: absolute; - height: 1px; - top: 0; - left: 0; - right: 0; - background-color: var(--border-color); - opacity: 0; - - @include animation-level(2) { - transition: opacity var(--transition-standard-in); - } - } - - &.has-border-top:before { - opacity: 1; - } } .checkbox-field { @@ -203,7 +167,7 @@ position: absolute; } - img, + img, video { object-fit: cover; width: 100%; diff --git a/src/scss/partials/popups/_popupComment.scss b/src/scss/partials/popups/_popupComment.scss new file mode 100644 index 0000000000..90bd532567 --- /dev/null +++ b/src/scss/partials/popups/_popupComment.scss @@ -0,0 +1,78 @@ +.popup-comment-container { + padding-bottom: 0 !important; + display: flex; + align-items: flex-end; + + .popup-comment { + display: flex; + justify-content: space-between; + align-items: center; + animation: appear .15s ease forwards; + + .popup-input-container { + width: 100%; + --height: 3.5rem; + --max-height: 8.375rem; + display: flex; + align-items: flex-end; + justify-content: space-between; + min-height: var(--height); + max-height: var(--max-height); + position: relative; + flex: 0 0 auto; + overflow: hidden; + + &:before { + content: " "; + position: absolute; + height: 1px; + top: 0; + left: 0; + right: 0; + background-color: var(--border-color); + opacity: 0; + + @include animation-level(2) { + transition: opacity var(--transition-standard-in); + } + } + + &.has-border-top:before { + opacity: 1; + } + } + + .emoticons-content { + overflow-y: auto; + } + + .toggle-emoticons { + width: 40px; + min-width: 40px; + padding: 0 !important; + height: 40px; + + &:before { + content: $tgico-smile; + } + + &.flip-icon:before { + content: $tgico-keyboard !important; + } + } + } + .btn-primary { + flex: 0 0 auto; + height: 2.5rem; + line-height: 2.5rem; + padding: 0 1rem; + text-transform: uppercase; + width: auto; + margin-left: .5rem; + margin-right: 1.5rem; + margin-bottom: 0.5rem; + &:hover { + background-color: var(--dark-primary-color) !important; + } + } +} diff --git a/src/scss/style.scss b/src/scss/style.scss index 6cf0c11c72..587e7a5c65 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -397,6 +397,7 @@ $chat-input-inner-padding-handhelds: .25rem; @import "partials/popups/datePicker"; @import "partials/popups/createPoll"; @import "partials/popups/forward"; +@import "partials/popups/popupComment"; @import "partials/popups/instanceDeactivated"; @import "partials/popups/joinChatInvite"; @import "partials/popups/reportMessages";