Stickers and Emoji tab

Stickers sorting
Stickers suggestion options
Group creation without members
Fix voting in polls
Fix processing autocomplete on settings change
This commit is contained in:
Eduard Kuzmenko 2023-03-06 17:05:15 +04:00
parent bb6cda7de5
commit 2408333c47
21 changed files with 512 additions and 229 deletions

View File

@ -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
};
}

View File

@ -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);

View File

@ -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;

View File

@ -789,6 +789,19 @@ export default class StickersTab extends EmoticonsTabC<StickersTabCategory<Stick
}
}));
rootScope.addEventListener('stickers_order', ({type, order}) => {
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) {

View File

@ -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;

View File

@ -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'

View File

@ -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);

View File

@ -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<MessagesAllStickers.messagesAllStickers>(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);
}
}

View File

@ -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() {

View File

@ -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) => {

View File

@ -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<typeof AppStickersAndEmojiTab['getInitArgs']>) {
this.container.classList.add('stickers-emoji-container');
this.setTitle('StickersName');
const promises: Promise<any>[] = [];
{
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<MessagesAllStickers.messagesAllStickers>(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);
}
}

View File

@ -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');
});

View File

@ -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,

View File

@ -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: {

View File

@ -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<T extends ButtonMenuItemOptionsVerifiable>({
buttons,
@ -21,7 +23,9 @@ export default function createContextMenu<T extends ButtonMenuItemOptionsVerifia
onOpen,
onClose,
onBeforeOpen,
listenerSetter: attachListenerSetter
listenerSetter: attachListenerSetter,
middleware,
listenForClick
}: {
buttons: T[],
findElement?: (e: MouseEvent | TouchEvent) => HTMLElement,
@ -31,60 +35,64 @@ export default function createContextMenu<T extends ButtonMenuItemOptionsVerifia
onOpen?: (target: HTMLElement) => 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<T extends ButtonMenuItemOptionsVerifia
cleanup();
buttons.forEach((button) => 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<T extends ButtonMenuItemOptionsVerifia
};
};
return {element, destroy};
if(middleware) {
middleware.onDestroy(() => {
destroy();
});
}
if(listenForClick) {
attachClickEvent(listenTo, open, {listenerSetter: attachListenerSetter});
}
return {element, destroy, open};
}

View File

@ -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',

View File

@ -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);
}

View File

@ -72,10 +72,7 @@ export class AppStickersManager extends AppManager {
private names: Record<string, InputStickerSet.inputStickerSetID>;
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
}
});
});
}
}

View File

@ -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;

View File

@ -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,

View File

@ -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%;