diff --git a/src/components/chat/chat.ts b/src/components/chat/chat.ts index b6325544..176b749c 100644 --- a/src/components/chat/chat.ts +++ b/src/components/chat/chat.ts @@ -689,7 +689,8 @@ export default class Chat extends EventListenerBase<{ replyToMsgId: this.input.replyToMsgId, scheduleDate: this.input.scheduleDate, silent: this.input.sendSilent, - sendAsPeerId: this.input.sendAsPeerId + sendAsPeerId: this.input.sendAsPeerId, + updateStickersetOrder: rootScope.settings.stickers.dynamicPackOrder }; } diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index b6a9a913..7efb7b11 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -2277,7 +2277,8 @@ export default class ChatInput { private async checkAutocomplete(value?: string, caretPos?: number, entities?: MessageEntity[]) { // return; - if(value === undefined) { + const hadValue = value !== undefined; + if(!hadValue) { const r = getRichValueWithCaret(this.messageInputField.input, true, true); value = r.value; caretPos = r.caretPos; @@ -2288,7 +2289,7 @@ export default class ChatInput { caretPos = value.length; } - if(entities === undefined) { + if(entities === undefined || !hadValue) { const _value = parseMarkdown(value, entities, true); entities = mergeEntities(entities, parseEntities(_value)); } @@ -2310,7 +2311,7 @@ export default class ChatInput { const firstChar = query[0]; if(this.stickersHelper && - rootScope.settings.stickers.suggest && + rootScope.settings.stickers.suggest !== 'none' && await this.chat.canSend('send_stickers') && entity?._ === 'messageEntityEmoji' && entity.length === value.length && !entity.offset) { foundHelper = this.stickersHelper; @@ -2731,7 +2732,7 @@ export default class ChatInput { } private getValueAndEntities(input: HTMLElement) { - const {entities: apiEntities, value} = getRichValueWithCaret(this.messageInput, true, false); + const {entities: apiEntities, value} = getRichValueWithCaret(input, true, false); const myEntities = parseEntities(value); const totalEntities = mergeEntities(apiEntities, myEntities); diff --git a/src/components/chat/stickersHelper.ts b/src/components/chat/stickersHelper.ts index 360f2c0e..03c1c8f9 100644 --- a/src/components/chat/stickersHelper.ts +++ b/src/components/chat/stickersHelper.ts @@ -68,12 +68,10 @@ export default class StickersHelper extends AutocompleteHelper { public checkEmoticon(emoticon: string) { const middleware = this.controller.getMiddleware(); - if(this.lazyLoadQueue) { - this.lazyLoadQueue.clear(); - } + this.lazyLoadQueue?.clear(); preloadAnimatedEmojiSticker(emoticon); - this.managers.appStickersManager.getStickersByEmoticon(emoticon) + this.managers.appStickersManager.getStickersByEmoticon(emoticon, true, rootScope.settings.stickers.suggest === 'all') .then((stickers) => { if(!middleware()) { return; diff --git a/src/components/emoticonsDropdown/tabs/stickers.ts b/src/components/emoticonsDropdown/tabs/stickers.ts index 55526cea..f13badda 100644 --- a/src/components/emoticonsDropdown/tabs/stickers.ts +++ b/src/components/emoticonsDropdown/tabs/stickers.ts @@ -789,6 +789,19 @@ export default class StickersTab extends EmoticonsTabC { + if(type !== 'stickers') { + return; + } + + order.forEach((id) => { + const category = this.categories[id]; + if(category) { + this.positionCategory(category, false); + } + }); + }); + rootScope.addEventListener('stickers_updated', ({type, stickers}) => { const category = this.categories[type === 'faved' ? 'faved' : 'recent']; if(category) { diff --git a/src/components/poll.ts b/src/components/poll.ts index abe79bbf..f81cc6ac 100644 --- a/src/components/poll.ts +++ b/src/components/poll.ts @@ -479,7 +479,7 @@ export default class PollElement extends HTMLElement { } } - clickHandler(e: Event) { + clickHandler = (e: Event) => { const target = findUpClassName(e.target, 'poll-answer') as HTMLElement; if(!target) { return; @@ -505,7 +505,7 @@ export default class PollElement extends HTMLElement { this.setResults([100, 0], answerIndex); target.classList.remove('is-voting'); }, 1000); */ - } + }; sendVotes(indexes: number[]) { if(this.sendVotePromise) return this.sendVotePromise; diff --git a/src/components/sidebarLeft/index.ts b/src/components/sidebarLeft/index.ts index b170a3b5..7cea8180 100644 --- a/src/components/sidebarLeft/index.ts +++ b/src/components/sidebarLeft/index.ts @@ -97,7 +97,7 @@ export class AppSidebarLeft extends SidebarSlider { const onNewGroupClick = () => { this.createTab(AppAddMembersTab).open({ type: 'chat', - skippable: false, + skippable: true, takeOut: (peerIds) => this.createTab(AppNewGroupTab).open(peerIds), title: 'GroupAddMembers', placeholder: 'SendMessageTo' diff --git a/src/components/sidebarLeft/tabs/addMembers.ts b/src/components/sidebarLeft/tabs/addMembers.ts index a21d49c7..e412a993 100644 --- a/src/components/sidebarLeft/tabs/addMembers.ts +++ b/src/components/sidebarLeft/tabs/addMembers.ts @@ -34,7 +34,7 @@ export default class AppAddMembersTab extends SliderSuperTab { const peerIds = this.selector.getSelected().map((sel) => sel.toPeerId()); const result = this.takeOut(peerIds); - if(this.skippable) { + if(this.skippable && !(result instanceof Promise)) { this.close(); } else if(result instanceof Promise) { this.attachToPromise(result); diff --git a/src/components/sidebarLeft/tabs/generalSettings.ts b/src/components/sidebarLeft/tabs/generalSettings.ts index 1e0b489a..718fcbe0 100644 --- a/src/components/sidebarLeft/tabs/generalSettings.ts +++ b/src/components/sidebarLeft/tabs/generalSettings.ts @@ -98,9 +98,7 @@ export class RangeSettingSelector { export default class AppGeneralSettingsTab extends SliderSuperTabEventable { public static getInitArgs() { return { - themes: rootScope.managers.appThemesManager.getThemes(), - allStickers: rootScope.managers.appStickersManager.getAllStickers(), - quickReaction: rootScope.managers.appReactionsManager.getQuickReaction() + themes: rootScope.managers.appThemesManager.getThemes() }; } @@ -529,140 +527,6 @@ export default class AppGeneralSettingsTab extends SliderSuperTabEventable { container.append(form); } - { - const container = section('Emoji'); - - const suggestCheckboxField = new CheckboxField({ - text: 'GeneralSettings.EmojiPrediction', - name: 'suggest-emoji', - stateKey: 'settings.emoji.suggest', - listenerSetter: this.listenerSetter - }); - const bigCheckboxField = new CheckboxField({ - text: 'GeneralSettings.BigEmoji', - name: 'emoji-big', - stateKey: 'settings.emoji.big', - listenerSetter: this.listenerSetter - }); - - container.append( - CreateRowFromCheckboxField(suggestCheckboxField).container, - CreateRowFromCheckboxField(bigCheckboxField).container - ); - } - - { - const section = new SettingSection({name: 'Telegram.InstalledStickerPacksController', caption: 'StickersBotInfo'}); - - const reactionsRow = new Row({ - titleLangKey: 'DoubleTapSetting', - havePadding: true, - clickable: () => { - this.slider.createTab(AppQuickReactionTab).open(); - }, - listenerSetter: this.listenerSetter - }); - - const renderQuickReaction = () => { - p.quickReaction.then((reaction) => { - if(reaction._ === 'availableReaction') { - return reaction.static_icon; - } else { - return this.managers.appEmojiManager.getCustomEmojiDocument(reaction.document_id); - } - }).then((doc) => { - wrapStickerToRow({ - row: reactionsRow, - doc, - size: 'small' - }); - }); - }; - - renderQuickReaction(); - - this.listenerSetter.add(rootScope)('quick_reaction', renderQuickReaction); - - const suggestCheckboxField = new CheckboxField({ - text: 'Stickers.SuggestStickers', - name: 'suggest', - stateKey: 'settings.stickers.suggest', - listenerSetter: this.listenerSetter - }); - const loopCheckboxField = new CheckboxField({ - text: 'InstalledStickers.LoopAnimated', - name: 'loop', - stateKey: 'settings.stickers.loop', - listenerSetter: this.listenerSetter - }); - - const stickerSets: {[id: string]: Row} = {}; - - const stickersContent = section.generateContentElement(); - - const lazyLoadQueue = new LazyLoadQueue(); - const renderStickerSet = (stickerSet: StickerSet.stickerSet, method: 'append' | 'prepend' = 'append') => { - const row = new Row({ - title: wrapEmojiText(stickerSet.title), - subtitleLangKey: 'Stickers', - subtitleLangArgs: [stickerSet.count], - havePadding: true, - clickable: () => { - new PopupStickers({id: stickerSet.id, access_hash: stickerSet.access_hash}).show(); - }, - listenerSetter: this.listenerSetter - }); - - stickerSets[stickerSet.id] = row; - - const div = document.createElement('div'); - div.classList.add('row-media'); - - wrapStickerSetThumb({ - set: stickerSet, - container: div, - group: 'GENERAL-SETTINGS', - lazyLoadQueue, - width: 36, - height: 36, - autoplay: true, - middleware: this.middlewareHelper.get() - }); - - row.container.append(div); - - stickersContent[method](row.container); - }; - - const promise = p.allStickers.then((allStickers) => { - assumeType(allStickers); - const promises = allStickers.sets.map((stickerSet) => renderStickerSet(stickerSet)); - return Promise.all(promises); - }); - - promises.push(promise); - - this.listenerSetter.add(rootScope)('stickers_installed', (set) => { - if(!stickerSets[set.id]) { - renderStickerSet(set, 'prepend'); - } - }); - - this.listenerSetter.add(rootScope)('stickers_deleted', (set) => { - if(stickerSets[set.id]) { - stickerSets[set.id].container.remove(); - delete stickerSets[set.id]; - } - }); - - section.content.append( - reactionsRow.container, - CreateRowFromCheckboxField(suggestCheckboxField).container, - CreateRowFromCheckboxField(loopCheckboxField).container - ); - this.scrollable.append(section.container); - } - return Promise.all(promises); } } diff --git a/src/components/sidebarLeft/tabs/newGroup.ts b/src/components/sidebarLeft/tabs/newGroup.ts index ad1540b3..332811ba 100644 --- a/src/components/sidebarLeft/tabs/newGroup.ts +++ b/src/components/sidebarLeft/tabs/newGroup.ts @@ -9,7 +9,7 @@ import appDialogsManager from '../../../lib/appManagers/appDialogsManager'; import InputField from '../../inputField'; import {SliderSuperTab} from '../../slider'; import AvatarEdit from '../../avatarEdit'; -import I18n from '../../../lib/langPack'; +import I18n, {joinElementsWith} from '../../../lib/langPack'; import ButtonCorner from '../../buttonCorner'; import getUserStatusString from '../../wrappers/getUserStatusString'; import appImManager from '../../../lib/appManagers/appImManager'; @@ -141,6 +141,10 @@ export default class AppNewGroupTab extends SliderSuperTab { nameArgs: [this.peerIds.length] }); + if(!this.peerIds.length) { + chatsSection.container.classList.add('hide'); + } + const list = this.list = appDialogsManager.createChatList({ new: true }); @@ -161,16 +165,34 @@ export default class AppNewGroupTab extends SliderSuperTab { this.groupLocationInputField.container.classList.add('hide'); } - return Promise.all(this.peerIds.map(async(userId) => { - const {dom} = appDialogsManager.addDialogNew({ - peerId: userId, - container: this.list, - rippleEnabled: false, - avatarSize: 'abitbigger' - }); + const usersPromise = Promise.all(this.peerIds.map((peerId) => this.managers.appUsersManager.getUser(peerId.toUserId()))); + const myUserPromise = this.managers.appUsersManager.getSelf(); - dom.lastMessageSpan.append(getUserStatusString(await this.managers.appUsersManager.getUser(userId))); - })); + const a = usersPromise.then((users) => { + return users.map((user) => { + const {dom} = appDialogsManager.addDialogNew({ + peerId: user.id.toPeerId(false), + container: this.list, + rippleEnabled: false, + avatarSize: 'abitbigger' + }); + + dom.lastMessageSpan.append(getUserStatusString(user)); + }) + }); + + const setTitlePromise = this.peerIds.length > 0 && this.peerIds.length < 5 ? Promise.all([usersPromise, myUserPromise]).then(([users, myUser]) => { + const names = users.map((user) => [user.first_name, user.last_name].filter(Boolean).join(' ')); + names.unshift(myUser.first_name); + + const joined = joinElementsWith(names, (isLast) => isLast ? ', ' : ' & ').join(''); + this.groupNameInputField.setDraftValue(joined); + }) : Promise.resolve(); + + return Promise.all([ + a, + setTitlePromise + ]); } public onCloseAfterTimeout() { diff --git a/src/components/sidebarLeft/tabs/settings.ts b/src/components/sidebarLeft/tabs/settings.ts index e4e2ee9c..33ac51ee 100644 --- a/src/components/sidebarLeft/tabs/settings.ts +++ b/src/components/sidebarLeft/tabs/settings.ts @@ -28,6 +28,7 @@ import {AccountAuthorizations, Authorization} from '../../../layer'; import PopupElement from '../../popups'; import {attachClickEvent} from '../../../helpers/dom/clickEvent'; import SettingSection from '../../settingSection'; +import AppStickersAndEmojiTab from './stickersAndEmoji'; export default class AppSettingsTab extends SliderSuperTab { private buttons: { @@ -192,7 +193,8 @@ export default class AppSettingsTab extends SliderSuperTab { m('data', 'DataSettings', AppDataAndStorageTab), m('lock', 'AccountSettings.PrivacyAndSecurity', AppPrivacyAndSecurityTab), m('settings', 'Telegram.GeneralSettingsViewController', AppGeneralSettingsTab), - m('folder', 'AccountSettings.Filters', AppChatFoldersTab) + m('folder', 'AccountSettings.Filters', AppChatFoldersTab), + m('stickers_face', 'StickersName', AppStickersAndEmojiTab) ]; const rows = b.map((item) => { diff --git a/src/components/sidebarLeft/tabs/stickersAndEmoji.ts b/src/components/sidebarLeft/tabs/stickersAndEmoji.ts new file mode 100644 index 00000000..64ddb4ee --- /dev/null +++ b/src/components/sidebarLeft/tabs/stickersAndEmoji.ts @@ -0,0 +1,301 @@ +/* + * https://github.com/morethanwords/tweb + * Copyright (C) 2019-2021 Eduard Kuzmenko + * https://github.com/morethanwords/tweb/blob/master/LICENSE + */ + +import forEachReverse from '../../../helpers/array/forEachReverse'; +import assumeType from '../../../helpers/assumeType'; +import createContextMenu from '../../../helpers/dom/createContextMenu'; +import positionElementByIndex from '../../../helpers/dom/positionElementByIndex'; +import Sortable from '../../../helpers/dom/sortable'; +import {StickerSet, MessagesAllStickers} from '../../../layer'; +import {i18n, LangPackKey} from '../../../lib/langPack'; +import wrapEmojiText from '../../../lib/richTextProcessor/wrapEmojiText'; +import rootScope from '../../../lib/rootScope'; +import CheckboxField from '../../checkboxField'; +import LazyLoadQueue from '../../lazyLoadQueue'; +import PopupStickers from '../../popups/stickers'; +import Row, {CreateRowFromCheckboxField} from '../../row'; +import SettingSection from '../../settingSection'; +import SliderSuperTab from '../../sliderTab'; +import wrapStickerSetThumb from '../../wrappers/stickerSetThumb'; +import wrapStickerToRow from '../../wrappers/stickerToRow'; +import AppQuickReactionTab from './quickReaction'; + +export default class AppStickersAndEmojiTab extends SliderSuperTab { + public static getInitArgs() { + return { + allStickers: rootScope.managers.appStickersManager.getAllStickers(), + quickReaction: rootScope.managers.appReactionsManager.getQuickReaction() + }; + } + + public init(p: ReturnType) { + this.container.classList.add('stickers-emoji-container'); + this.setTitle('StickersName'); + + const promises: Promise[] = []; + + { + const section = new SettingSection({caption: 'LoopAnimatedStickersInfo'}); + + const suggestStickersRow = new Row({ + icon: 'lamp', + titleLangKey: 'Stickers.SuggestStickers', + clickable: true, + listenerSetter: this.listenerSetter, + titleRightSecondary: true + }); + + const map: {[k in typeof rootScope.settings.stickers.suggest]: LangPackKey} = { + all: 'SuggestStickersAll', + installed: 'SuggestStickersInstalled', + none: 'SuggestStickersNone' + }; + + const setStickersSuggestDescription = () => { + suggestStickersRow.titleRight.replaceChildren(i18n(map[rootScope.settings.stickers.suggest])); + }; + + setStickersSuggestDescription(); + + const setStickersSuggest = (value: typeof rootScope.settings.stickers.suggest) => { + if(rootScope.settings.stickers.suggest === value) return; + rootScope.settings.stickers.suggest = value; + setStickersSuggestDescription(); + return this.managers.appStateManager.setByKey('settings.stickers.suggest', value); + }; + + createContextMenu({ + buttons: [{ + icon: 'stickers_face', + text: 'SuggestStickersAll', + onClick: setStickersSuggest.bind(this, 'all') + }, { + icon: 'newprivate', + text: 'SuggestStickersInstalled', + onClick: setStickersSuggest.bind(this, 'installed') + }, { + icon: 'stop', + text: 'SuggestStickersNone', + onClick: setStickersSuggest.bind(this, 'none') + }], + listenTo: suggestStickersRow.container, + middleware: this.middlewareHelper.get(), + listenForClick: true + }); + + const reactionsRow = new Row({ + titleLangKey: 'DoubleTapSetting', + havePadding: true, + clickable: () => { + this.slider.createTab(AppQuickReactionTab).open(); + }, + listenerSetter: this.listenerSetter + }); + + const renderQuickReaction = () => { + p.quickReaction.then((reaction) => { + if(reaction._ === 'availableReaction') { + return reaction.static_icon; + } else { + return this.managers.appEmojiManager.getCustomEmojiDocument(reaction.document_id); + } + }).then((doc) => { + wrapStickerToRow({ + row: reactionsRow, + doc, + size: 'small' + }); + }); + }; + + renderQuickReaction(); + + this.listenerSetter.add(rootScope)('quick_reaction', () => { + p = AppStickersAndEmojiTab.getInitArgs(); + renderQuickReaction(); + }); + + const loopStickersRow = new Row({ + icon: 'flip', + titleLangKey: 'InstalledStickers.LoopAnimated', + checkboxField: new CheckboxField({ + name: 'loop', + stateKey: 'settings.stickers.loop', + listenerSetter: this.listenerSetter, + toggle: true + }), + listenerSetter: this.listenerSetter + }); + + section.content.append( + reactionsRow.container, + suggestStickersRow.container, + loopStickersRow.container + ); + + this.scrollable.append(section.container); + } + + { + const section = new SettingSection({name: 'Emoji'}); + + const suggestEmojiRow = new Row({ + icon: 'lamp', + titleLangKey: 'GeneralSettings.EmojiPrediction', + checkboxField: new CheckboxField({ + name: 'suggest-emoji', + stateKey: 'settings.emoji.suggest', + listenerSetter: this.listenerSetter, + toggle: true + }), + listenerSetter: this.listenerSetter + }); + const bigEmojiRow = new Row({ + icon: 'smile', + titleLangKey: 'GeneralSettings.BigEmoji', + checkboxField: new CheckboxField({ + name: 'emoji-big', + stateKey: 'settings.emoji.big', + listenerSetter: this.listenerSetter, + toggle: true + }), + listenerSetter: this.listenerSetter + }); + + section.content.append( + suggestEmojiRow.container, + bigEmojiRow.container + ); + + this.scrollable.append(section.container); + } + + { + const section = new SettingSection({name: 'DynamicPackOrder', caption: 'DynamicPackOrderInfo'}); + + const dynamicPackOrderRow = new Row({ + titleLangKey: 'DynamicPackOrder', + checkboxField: new CheckboxField({ + name: 'dynamic-pack-order', + stateKey: 'settings.stickers.dynamicPackOrder', + listenerSetter: this.listenerSetter, + toggle: true + }), + listenerSetter: this.listenerSetter + }); + + section.content.append( + dynamicPackOrderRow.container + ); + + this.scrollable.append(section.container); + } + + { + const section = new SettingSection({name: 'Telegram.InstalledStickerPacksController', caption: 'StickersBotInfo'}); + + const stickerSets: {[id: string]: Row} = {}; + + const stickersContent = section.generateContentElement(); + + const lazyLoadQueue = new LazyLoadQueue(); + const renderStickerSet = (stickerSet: StickerSet.stickerSet, method: 'append' | 'prepend' = 'append') => { + const row = new Row({ + title: wrapEmojiText(stickerSet.title), + subtitleLangKey: 'Stickers', + subtitleLangArgs: [stickerSet.count], + havePadding: true, + clickable: () => { + new PopupStickers({id: stickerSet.id, access_hash: stickerSet.access_hash}).show(); + }, + listenerSetter: this.listenerSetter + }); + + row.container.dataset.id = '' + stickerSet.id; + + row.makeSortable(); + + stickerSets[stickerSet.id] = row; + + const div = document.createElement('div'); + div.classList.add('row-media'); + + wrapStickerSetThumb({ + set: stickerSet, + container: div, + group: 'GENERAL-SETTINGS', + lazyLoadQueue, + width: 36, + height: 36, + autoplay: true, + middleware: this.middlewareHelper.get() + }); + + row.container.append(div); + + stickersContent[method](row.container); + }; + + const promise = p.allStickers.then((allStickers) => { + assumeType(allStickers); + const promises = allStickers.sets.map((stickerSet) => renderStickerSet(stickerSet)); + return Promise.all(promises); + }); + + promises.push(promise); + + this.listenerSetter.add(rootScope)('stickers_installed', (set) => { + if(!stickerSets[set.id]) { + renderStickerSet(set, 'prepend'); + } + }); + + this.listenerSetter.add(rootScope)('stickers_deleted', (set) => { + if(stickerSets[set.id]) { + stickerSets[set.id].container.remove(); + delete stickerSets[set.id]; + } + }); + + this.listenerSetter.add(rootScope)('stickers_order', ({type, order}) => { + if(type !== 'stickers') { + return; + } + + order.forEach((id, idx) => { + const row = stickerSets[id]; + if(!row) { + return; + } + + positionElementByIndex(row.container, stickersContent, idx) + }); + }); + + this.listenerSetter.add(rootScope)('stickers_top', (id) => { + const row = stickerSets[id]; + if(!row) { + return; + } + + positionElementByIndex(row.container, stickersContent, 0); + }); + + new Sortable({ + list: stickersContent, + middleware: this.middlewareHelper.get(), + onSort: (idx, newIdx) => { + const order = Array.from(stickersContent.children).map((el) => (el as HTMLElement).dataset.id); + this.managers.appStickersManager.reorderStickerSets(order); + } + }); + + this.scrollable.append(section.container); + } + + return Promise.all(promises); + } +} diff --git a/src/components/wrappers/sticker.ts b/src/components/wrappers/sticker.ts index 8aa510dd..42e50a31 100644 --- a/src/components/wrappers/sticker.ts +++ b/src/components/wrappers/sticker.ts @@ -132,8 +132,8 @@ export default async function wrapSticker({doc, div, middleware, loadStickerMidd div.dataset.stickerEmoji = emoji; } - div.dataset.stickerPlay = '' + +play; - div.dataset.stickerLoop = '' + +loop; + div.dataset.stickerPlay = '' + +(play || false); + div.dataset.stickerLoop = '' + +(loop || false); div.classList.add('media-sticker-wrapper'); }); diff --git a/src/config/app.ts b/src/config/app.ts index 4ba4da1d..a8784273 100644 --- a/src/config/app.ts +++ b/src/config/app.ts @@ -21,7 +21,7 @@ const App = { version: process.env.VERSION, versionFull: process.env.VERSION_FULL, build: +process.env.BUILD, - langPackVersion: '0.9.8', + langPackVersion: '1.0.1', langPack: 'webk', langPackCode: 'en', domains: MAIN_DOMAINS, diff --git a/src/config/state.ts b/src/config/state.ts index 727cde94..9804a195 100644 --- a/src/config/state.ts +++ b/src/config/state.ts @@ -91,7 +91,8 @@ export type State = { videos: boolean }, stickers: { - suggest: boolean, + suggest: 'all' | 'installed' | 'none', + dynamicPackOrder: boolean, loop: boolean }, emoji: { @@ -267,7 +268,8 @@ export const STATE_INIT: State = { video_upload_maxbitrate: 100 }, stickers: { - suggest: true, + suggest: 'all', + dynamicPackOrder: true, loop: true }, emoji: { diff --git a/src/helpers/dom/createContextMenu.ts b/src/helpers/dom/createContextMenu.ts index 68e86907..d4032059 100644 --- a/src/helpers/dom/createContextMenu.ts +++ b/src/helpers/dom/createContextMenu.ts @@ -6,11 +6,13 @@ import ButtonMenu, {ButtonMenuItemOptionsVerifiable} from '../../components/buttonMenu'; import filterAsync from '../array/filterAsync'; +import callbackify from '../callbackify'; import contextMenuController from '../contextMenuController'; import ListenerSetter from '../listenerSetter'; -import {getMiddleware} from '../middleware'; +import {getMiddleware, Middleware} from '../middleware'; import positionMenu from '../positionMenu'; import {attachContextMenuListener} from './attachContextMenuListener'; +import {attachClickEvent} from './clickEvent'; export default function createContextMenu({ buttons, @@ -21,7 +23,9 @@ export default function createContextMenu HTMLElement, @@ -31,60 +35,64 @@ export default function createContextMenu any, onClose?: () => any, onBeforeOpen?: () => any, - listenerSetter?: ListenerSetter + listenerSetter?: ListenerSetter, + middleware?: Middleware, + listenForClick?: boolean }) { appendTo ??= document.body; attachListenerSetter ??= new ListenerSetter(); const listenerSetter = new ListenerSetter(); - const middleware = getMiddleware(); + const middlewareHelper = middleware ? middleware.create() : getMiddleware(); let element: HTMLElement; - attachContextMenuListener({ - element: listenTo, - callback: (e) => { - const target = findElement ? findElement(e as any) : listenTo; - if(!target) { + const open = (e: MouseEvent | TouchEvent) => { + const target = findElement ? findElement(e as any) : listenTo; + if(!target) { + return; + } + + let _element = element; + if(e instanceof MouseEvent || e.hasOwnProperty('preventDefault')) (e as any).preventDefault(); + if(_element && _element.classList.contains('active')) { + return false; + } + if(e instanceof MouseEvent || e.hasOwnProperty('cancelBubble')) (e as any).cancelBubble = true; + + const r = async() => { + await onOpen?.(target); + + const initResult = await init(); + if(!initResult) { return; } - let _element = element; - if(e instanceof MouseEvent || e.hasOwnProperty('preventDefault')) (e as any).preventDefault(); - if(_element && _element.classList.contains('active')) { - return false; - } - if(e instanceof MouseEvent || e.hasOwnProperty('cancelBubble')) (e as any).cancelBubble = true; + _element = initResult.element; + const {cleanup, destroy} = initResult; - const r = async() => { - await onOpen?.(target); + positionMenu(e, _element); + contextMenuController.openBtnMenu(_element, () => { + onClose?.(); + cleanup(); - const initResult = await init(); - if(!initResult) { - return; - } + setTimeout(() => { + destroy(); + }, 300); + }); + }; - _element = initResult.element; - const {cleanup, destroy} = initResult; + r(); + }; - positionMenu(e, _element); - contextMenuController.openBtnMenu(_element, () => { - onClose?.(); - cleanup(); - - setTimeout(() => { - destroy(); - }, 300); - }); - }; - - r(); - }, + attachContextMenuListener({ + element: listenTo, + callback: open, listenerSetter: attachListenerSetter }); const cleanup = () => { listenerSetter.removeAll(); - middleware.clean(); + middlewareHelper.clean(); }; const destroy = () => { @@ -96,7 +104,9 @@ export default function createContextMenu button.element = undefined); - const f = filterButtons || ((buttons: T[]) => filterAsync(buttons, (button) => button?.verify ? button.verify() : true)); + const f = filterButtons || ((buttons: T[]) => filterAsync(buttons, (button) => { + return button?.verify ? callbackify(button.verify(), (result) => result ?? false) : true; + })); const filteredButtons = await f(buttons); if(!filteredButtons.length) { @@ -122,5 +132,15 @@ export default function createContextMenu { + destroy(); + }); + } + + if(listenForClick) { + attachClickEvent(listenTo, open, {listenerSetter: attachListenerSetter}); + } + + return {element, destroy, open}; } diff --git a/src/lang.ts b/src/lang.ts index 081eaa76..497eb274 100644 --- a/src/lang.ts +++ b/src/lang.ts @@ -933,6 +933,13 @@ const lang = { 'ErrorSendRestrictedPollsAll': 'Sorry, sending polls is not allowed in this group.', 'Remove': 'Remove', 'ChannelBlockUser': 'Remove User', + 'StickersName': 'Stickers and Emoji', + 'LoopAnimatedStickersInfo': 'Animated stickers will play continuously in chats.', + 'SuggestStickersAll': 'All Sets', + 'SuggestStickersInstalled': 'My Sets', + 'SuggestStickersNone': 'None', + 'DynamicPackOrder': 'Dynamic Pack Order', + 'DynamicPackOrderInfo': 'Automatically place recently used sticker packs at the front of the panel.', // * macos 'AccountSettings.Filters': 'Chat Folders', diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index f243ddd2..d595e05e 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -166,14 +166,13 @@ const processAfter = (cb: () => void) => { cb(); }; -const UPDATE_STICKERSET_ORDER = true; - export type MessageSendingParams = Partial<{ threadId: number, replyToMsgId: number, scheduleDate: number, silent: boolean, sendAsPeerId: number, + updateStickersetOrder: boolean }>; export class AppMessagesManager extends AppManager { @@ -657,7 +656,7 @@ export class AppMessagesManager extends AppManager { schedule_date: options.scheduleDate || undefined, silent: options.silent, send_as: sendAs, - update_stickersets_order: UPDATE_STICKERSET_ORDER + update_stickersets_order: options.updateStickersetOrder }, sentRequestOptions); } @@ -1146,7 +1145,8 @@ export class AppMessagesManager extends AppManager { silent: options.silent, entities, clear_draft: options.clearDraft, - send_as: options.sendAsPeerId ? this.appPeersManager.getInputPeerById(options.sendAsPeerId) : undefined + send_as: options.sendAsPeerId ? this.appPeersManager.getInputPeerById(options.sendAsPeerId) : undefined, + update_stickersets_order: options.updateStickersetOrder }).then((updates) => { this.apiUpdatesManager.processUpdateMessage(updates); }, (error: ApiError) => { @@ -1272,7 +1272,7 @@ export class AppMessagesManager extends AppManager { silent: options.silent, clear_draft: options.clearDraft, send_as: options.sendAsPeerId ? this.appPeersManager.getInputPeerById(options.sendAsPeerId) : undefined, - update_stickersets_order: UPDATE_STICKERSET_ORDER + update_stickersets_order: options.updateStickersetOrder }).then((updates) => { this.apiUpdatesManager.processUpdateMessage(updates); deferred.resolve(); @@ -1488,7 +1488,7 @@ export class AppMessagesManager extends AppManager { schedule_date: options.scheduleDate, silent: options.silent, send_as: sendAs, - update_stickersets_order: UPDATE_STICKERSET_ORDER + update_stickersets_order: options.updateStickersetOrder }, sentRequestOptions); } diff --git a/src/lib/appManagers/appStickersManager.ts b/src/lib/appManagers/appStickersManager.ts index ee51db01..a4d4b391 100644 --- a/src/lib/appManagers/appStickersManager.ts +++ b/src/lib/appManagers/appStickersManager.ts @@ -72,10 +72,7 @@ export class AppStickersManager extends AppManager { private names: Record; protected after() { - this.getStickerSetPromises = {}; - this.getStickersByEmoticonsPromises = {}; - this.sounds = {}; - this.names = {}; + this.clear(true); this.rootScope.addEventListener('user_auth', () => { setTimeout(() => { @@ -83,8 +80,8 @@ export class AppStickersManager extends AppManager { // this.getFavedStickersStickers(); }, 1000); - if(!this.getGreetingStickersPromise && this.getGreetingStickersTimeout === undefined) { - this.getGreetingStickersTimeout = ctx.setTimeout(() => { + if(!this.getGreetingStickersPromise) { + this.getGreetingStickersTimeout ??= ctx.setTimeout(() => { this.getGreetingStickersTimeout = undefined; this.getGreetingSticker(true); }, 5000); @@ -106,10 +103,41 @@ export class AppStickersManager extends AppManager { updateMoveStickerSetToTop: (update) => { this.rootScope.dispatchEvent('stickers_top', update.stickerset); + }, + + updateStickerSetsOrder: (update) => { + this.rootScope.dispatchEvent('stickers_order', { + type: update.pFlags.emojis ? 'emojis' : (update.pFlags.masks ? 'masks' : 'stickers'), + order: update.order + }); } + + // updateStickerSets: (update) => { + // if(update.pFlags.masks) { + // return; + // } + + // this.storage.clear(false); + + // if(update.pFlags.emojis) { + + // } else { + // this.favedStickers = undefined; + // this.recentStickers = undefined; + // this.onStickersUpdated('recent', true); + // this.onStickersUpdated('faved', true); + // } + // } }); } + public clear = (init?: boolean) => { + this.getStickerSetPromises = {}; + this.getStickersByEmoticonsPromises = {}; + this.sounds = {}; + this.names = {}; + }; + private async onStickersUpdated(type: 'faved' | 'recent', overwrite: boolean) { const stickers = await (type === 'faved' ? this.getFavedStickersStickers(overwrite) : this.getRecentStickersStickers(overwrite)); this.rootScope.dispatchEvent('stickers_updated', { @@ -673,27 +701,29 @@ export class AppStickersManager extends AppManager { public preloadStickerSets() { return this.getAllStickers().then((allStickers) => { - return Promise.all((allStickers as MessagesAllStickers.messagesAllStickers).sets.map((set) => this.getStickerSet(set, {useCache: true}))); + const sets = (allStickers as MessagesAllStickers.messagesAllStickers).sets; + return Promise.all(sets.map((set) => this.getStickerSet(set, {useCache: true}))); }); } // TODO: detect "🤷" by "🤷‍♂️" - public getStickersByEmoticon(emoticon: string, includeOurStickers = true) { + public getStickersByEmoticon(emoticon: string, includeOurStickers = true, includeServerStickers = true) { emoticon = fixEmoji(emoticon); - if(this.getStickersByEmoticonsPromises[emoticon]) return this.getStickersByEmoticonsPromises[emoticon]; + const cacheKey = emoticon + (includeOurStickers ? '1' : '0') + (includeServerStickers ? '1' : '0'); + if(this.getStickersByEmoticonsPromises[cacheKey]) return this.getStickersByEmoticonsPromises[cacheKey]; - return this.getStickersByEmoticonsPromises[emoticon] = Promise.all([ - this.apiManager.invokeApiHashable({ + return this.getStickersByEmoticonsPromises[cacheKey] = Promise.all([ + includeServerStickers ? this.apiManager.invokeApiHashable({ method: 'messages.getStickers', params: { emoticon }, processResult: (stickers) => stickers - }), + }) : undefined, includeOurStickers ? this.preloadStickerSets() : [], includeOurStickers ? this.getRecentStickers() : undefined ]).then(([messagesStickers, installedSets, recentStickers]) => { - const foundStickers = (messagesStickers as MessagesStickers.messagesStickers).stickers.map((sticker) => this.appDocsManager.saveDoc(sticker)); + const foundStickers = messagesStickers ? (messagesStickers as MessagesStickers.messagesStickers).stickers.map((sticker) => this.appDocsManager.saveDoc(sticker)) : []; const cachedStickersAnimated: Document.document[] = [], cachedStickersStatic: Document.document[] = []; // console.log('getStickersByEmoticon', messagesStickers, installedSets, recentStickers); @@ -755,4 +785,21 @@ export class AppStickersManager extends AppManager { return this.apiManager.invokeApi('messages.clearRecentStickers'); } + + public reorderStickerSets(order: StickerSet.stickerSet['id'][], emojis?: boolean, masks?: boolean) { + return this.apiManager.invokeApi('messages.reorderStickerSets', { + emojis, + masks, + order + }).then(() => { + this.apiUpdatesManager.processLocalUpdate({ + _: 'updateStickerSetsOrder', + order, + pFlags: { + emojis: emojis || undefined, + masks: masks || undefined + } + }); + }); + } } diff --git a/src/lib/appManagers/utils/state/loadState.ts b/src/lib/appManagers/utils/state/loadState.ts index 475db22b..528990b7 100644 --- a/src/lib/appManagers/utils/state/loadState.ts +++ b/src/lib/appManagers/utils/state/loadState.ts @@ -383,6 +383,10 @@ async function loadStateInner() { state.settings.liteMode.gif = !state.settings.autoPlay.gifs; } + if(state.build < 312 && typeof(state.settings.stickers.suggest) === 'boolean') { + state.settings.stickers.suggest = state.settings.stickers.suggest ? 'all' : 'none'; + } + if(compareVersion(state.version, STATE_VERSION) !== 0) { newVersion = STATE_VERSION; oldVersion = state.version; diff --git a/src/lib/rootScope.ts b/src/lib/rootScope.ts index a407e918..9b416758 100644 --- a/src/lib/rootScope.ts +++ b/src/lib/rootScope.ts @@ -93,6 +93,7 @@ export type BroadcastEvents = { 'stickers_deleted': StickerSet.stickerSet, 'stickers_updated': {type: 'recent' | 'faved', stickers: MyDocument[]}, 'stickers_top': Long, + 'stickers_order': {type: 'masks' | 'emojis' | 'stickers', order: Long[]}, 'sticker_updated': {type: 'recent' | 'faved', document: MyDocument, faved: boolean}, 'state_cleared': void, diff --git a/src/scss/partials/pages/_chats.scss b/src/scss/partials/pages/_chats.scss index 1be3a66c..6f512d85 100644 --- a/src/scss/partials/pages/_chats.scss +++ b/src/scss/partials/pages/_chats.scss @@ -10,11 +10,11 @@ display: flex; max-width: calc(#{$large-screen} + 2px) !important; - .avatar-edit { - .tgico-cameraadd { - top: 52%; - } - } + // .avatar-edit { + // .tgico-cameraadd { + // top: 52%; + // } + // } #main-columns { width: 100%;