diff --git a/src/components/appSearch.ts b/src/components/appSearch.ts index 76dc5c98..5f6fad05 100644 --- a/src/components/appSearch.ts +++ b/src/components/appSearch.ts @@ -72,7 +72,7 @@ export default class AppSearch { private query = ''; - public listsContainer: HTMLDivElement = null; + private listsContainer: HTMLDivElement = null; private peerId = 0; // 0 - means global private threadId = 0; diff --git a/src/components/chat/autocompleteHelper.ts b/src/components/chat/autocompleteHelper.ts index ea373a3f..e2f83769 100644 --- a/src/components/chat/autocompleteHelper.ts +++ b/src/components/chat/autocompleteHelper.ts @@ -6,10 +6,12 @@ import attachListNavigation from "../../helpers/dom/attachlistNavigation"; import EventListenerBase from "../../helpers/eventListenerBase"; +import { safeAssign } from "../../helpers/object"; import { isMobile } from "../../helpers/userAgent"; import rootScope from "../../lib/rootScope"; import appNavigationController, { NavigationItem } from "../appNavigationController"; import SetTransition from "../singleTransition"; +import AutocompleteHelperController from "./autocompleteHelperController"; export default class AutocompleteHelper extends EventListenerBase<{ hidden: () => void, @@ -21,19 +23,30 @@ export default class AutocompleteHelper extends EventListenerBase<{ protected resetTarget: () => void; protected init?(): void; - constructor(appendTo: HTMLElement, - protected listType: 'xy' | 'x' | 'y', - protected onSelect: (target: Element) => boolean | void, - protected waitForKey?: string - ) { + protected controller: AutocompleteHelperController; + protected listType: 'xy' | 'x' | 'y'; + protected onSelect: (target: Element) => boolean | void; + protected waitForKey?: string; + + constructor(options: { + appendTo: HTMLElement, + controller: AutocompleteHelper['controller'], + listType: AutocompleteHelper['listType'], + onSelect: AutocompleteHelper['onSelect'], + waitForKey?: AutocompleteHelper['waitForKey'] + }) { super(false); + safeAssign(this, options); + this.container = document.createElement('div'); this.container.classList.add('autocomplete-helper', 'z-depth-1'); - - appendTo.append(this.container); - + + options.appendTo.append(this.container); + this.attachNavigation(); + + this.controller.addHelper(this); } protected onVisible = () => { @@ -89,7 +102,14 @@ export default class AutocompleteHelper extends EventListenerBase<{ } this.hidden = hide; - !this.hidden && this.dispatchEvent('visible'); // fire it before so target will be set + + if(!this.hidden) { + this.controller.hideOtherHelpers(this); + this.dispatchEvent('visible'); // fire it before so target will be set + } else { + this.controller.hideOtherHelpers(); + } + SetTransition(this.container, 'is-visible', !hide, rootScope.settings.animationsEnabled ? 200 : 0, () => { this.hidden && this.dispatchEvent('hidden'); }); diff --git a/src/components/chat/autocompleteHelperController.ts b/src/components/chat/autocompleteHelperController.ts index e3710cda..9681f6f6 100644 --- a/src/components/chat/autocompleteHelperController.ts +++ b/src/components/chat/autocompleteHelperController.ts @@ -1,21 +1,33 @@ +import { getMiddleware } from "../../helpers/middleware"; import AutocompleteHelper from "./autocompleteHelper"; export default class AutocompleteHelperController { private helpers: Set = new Set(); + private middleware = getMiddleware(); + /* private tempId = 0; - public addHelpers(helpers: AutocompleteHelper[]) { - for(const helper of helpers) { - this.helpers.add(helper); - } + public incrementToggleCount() { + return ++this.tempId; } - public toggleHelper(helper: AutocompleteHelper, hide?: boolean) { + public getToggleCount() { + return this.tempId; + } */ + + public getMiddleware() { + this.middleware.clean(); + return this.middleware.get(); + } + + public addHelper(helper: AutocompleteHelper) { + this.helpers.add(helper); + } + + public hideOtherHelpers(helper?: AutocompleteHelper) { this.helpers.forEach(h => { if(h !== helper) { - helper.toggle(true); + h.toggle(true); } }); - - helper.toggle(hide); } } diff --git a/src/components/chat/bubbles.ts b/src/components/chat/bubbles.ts index 4e8822cc..fca3499a 100644 --- a/src/components/chat/bubbles.ts +++ b/src/components/chat/bubbles.ts @@ -78,9 +78,9 @@ let TEST_SCROLL = TEST_SCROLL_TIMES; let queueId = 0; export default class ChatBubbles { - bubblesContainer: HTMLDivElement; - chatInner: HTMLDivElement; - scrollable: Scrollable; + public bubblesContainer: HTMLDivElement; + private chatInner: HTMLDivElement; + public scrollable: Scrollable; private getHistoryTopPromise: Promise; private getHistoryBottomPromise: Promise; @@ -88,11 +88,11 @@ export default class ChatBubbles { public peerId = 0; //public messagesCount: number = -1; - public unreadOut = new Set(); + private unreadOut = new Set(); public needUpdate: {replyToPeerId: number, replyMid: number, mid: number}[] = []; // if need wrapSingleMessage public bubbles: {[mid: string]: HTMLDivElement} = {}; - public dateMessages: {[timestamp: number]: { + private dateMessages: {[timestamp: number]: { div: HTMLDivElement, firstTimestamp: number, container: HTMLDivElement, @@ -109,14 +109,14 @@ export default class ChatBubbles { private unreadedSeen: Set = new Set(); private readPromise: Promise; - public bubbleGroups: BubbleGroups; + private bubbleGroups: BubbleGroups; private preloader: ProgressivePreloader = null; private loadedTopTimes = 0; private loadedBottomTimes = 0; - public messagesQueuePromise: Promise = null; + private messagesQueuePromise: Promise = null; private messagesQueue: {message: any, bubble: HTMLDivElement, reverse: boolean, promises: Promise[]}[] = []; private messagesQueueOnRender: () => void = null; private messagesQueueOnRenderAdditional: () => void = null; @@ -132,12 +132,12 @@ export default class ChatBubbles { public listenerSetter: ListenerSetter; - public replyFollowHistory: number[] = []; + private replyFollowHistory: number[] = []; - public isHeavyAnimationInProgress = false; - public scrollingToNewBubble: HTMLElement; + private isHeavyAnimationInProgress = false; + private scrollingToNewBubble: HTMLElement; - public isFirstLoad = true; + private isFirstLoad = true; private needReflowScroll: boolean; private fetchNewPromise: Promise; diff --git a/src/components/chat/chat.ts b/src/components/chat/chat.ts index 2c42ce4f..589e298f 100644 --- a/src/components/chat/chat.ts +++ b/src/components/chat/chat.ts @@ -37,6 +37,8 @@ import { fastRaf } from "../../helpers/schedulers"; import AppPrivateSearchTab from "../sidebarRight/tabs/search"; import type { State } from "../../lib/appManagers/appStateManager"; import renderImageFromUrl from "../../helpers/dom/renderImageFromUrl"; +import mediaSizes from "../../helpers/mediaSizes"; +import ChatSearch from "./search"; export type ChatType = 'chat' | 'pinned' | 'replies' | 'discussion' | 'scheduled'; @@ -371,4 +373,19 @@ export default class Chat extends EventListenerBase<{ public isAnyGroup() { return this.peerId === rootScope.myId || this.peerId === REPLIES_PEER_ID || this.appPeersManager.isAnyGroup(this.peerId); } + + public initSearch(query?: string) { + if(!this.peerId) return; + + if(mediaSizes.isMobile) { + new ChatSearch(this.topbar, this, query); + } else { + let tab = appSidebarRight.getTab(AppPrivateSearchTab); + if(!tab) { + tab = new AppPrivateSearchTab(appSidebarRight); + } + + tab.open(this.peerId, this.threadId, this.bubbles.onDatePick, query); + } + } } diff --git a/src/components/chat/commandsHelper.ts b/src/components/chat/commandsHelper.ts index 55cc22d2..d547885c 100644 --- a/src/components/chat/commandsHelper.ts +++ b/src/components/chat/commandsHelper.ts @@ -1,15 +1,24 @@ +import type ChatInput from "./input"; import { BotCommand } from "../../layer"; import RichTextProcessor from "../../lib/richtextprocessor"; import AvatarElement from "../avatar"; import Scrollable from "../scrollable"; import AutocompleteHelper from "./autocompleteHelper"; +import AutocompleteHelperController from "./autocompleteHelperController"; export default class CommandsHelper extends AutocompleteHelper { private scrollable: Scrollable; - constructor(appendTo: HTMLElement) { - super(appendTo, 'y', (target) => { - + constructor(appendTo: HTMLElement, controller: AutocompleteHelperController, private chatInput: ChatInput) { + super({ + appendTo, + controller, + listType: 'y', + onSelect: (target) => { + const command = target.querySelector('.commands-helper-command-name').innerHTML; + chatInput.messageInput.innerHTML = command; + chatInput.sendMessage(); + } }); this.container.classList.add('commands-helper'); diff --git a/src/components/chat/emojiHelper.ts b/src/components/chat/emojiHelper.ts index 66c5684a..3e82c80b 100644 --- a/src/components/chat/emojiHelper.ts +++ b/src/components/chat/emojiHelper.ts @@ -2,13 +2,19 @@ import type ChatInput from "./input"; import { appendEmoji, getEmojiFromElement } from "../emoticonsDropdown/tabs/emoji"; import { ScrollableX } from "../scrollable"; import AutocompleteHelper from "./autocompleteHelper"; +import AutocompleteHelperController from "./autocompleteHelperController"; export default class EmojiHelper extends AutocompleteHelper { private scrollable: ScrollableX; - constructor(appendTo: HTMLElement, private chatInput: ChatInput) { - super(appendTo, 'x', (target) => { - this.chatInput.onEmojiSelected(getEmojiFromElement(target as any), true); + constructor(appendTo: HTMLElement, controller: AutocompleteHelperController, private chatInput: ChatInput) { + super({ + appendTo, + controller, + listType: 'x', + onSelect: (target) => { + this.chatInput.onEmojiSelected(getEmojiFromElement(target as any), true); + } }); this.container.classList.add('emoji-helper'); diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index 206f710a..b5262083 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -63,6 +63,8 @@ import setRichFocus from '../../helpers/dom/setRichFocus'; import SearchIndex from '../../lib/searchIndex'; import CommandsHelper from './commandsHelper'; import AutocompleteHelperController from './autocompleteHelperController'; +import AutocompleteHelper from './autocompleteHelper'; +import appUsersManager from '../../lib/appManagers/appUsersManager'; const RECORD_MIN_TIME = 500; const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.'; @@ -369,11 +371,9 @@ export default class ChatInput { this.rowsWrapper.append(this.replyElements.container); this.autocompleteHelperController = new AutocompleteHelperController(); - this.autocompleteHelperController.addHelpers([ - this.commandsHelper = new CommandsHelper(this.rowsWrapper), - this.emojiHelper = new EmojiHelper(this.rowsWrapper, this), - this.stickersHelper = new StickersHelper(this.rowsWrapper) - ]); + this.stickersHelper = new StickersHelper(this.rowsWrapper, this.autocompleteHelperController); + this.emojiHelper = new EmojiHelper(this.rowsWrapper, this.autocompleteHelperController, this); + this.commandsHelper = new CommandsHelper(this.rowsWrapper, this.autocompleteHelperController, this); this.rowsWrapper.append(this.newMessageWrapper); this.btnCancelRecord = ButtonIcon('delete danger btn-circle z-depth-1 btn-record-cancel'); @@ -433,11 +433,12 @@ export default class ChatInput { this.listenerSetter.add(rootScope, 'settings_updated', () => { if(this.stickersHelper) { - if(!rootScope.settings.stickers.suggest) { + this.checkAutocomplete(); + /* if(!rootScope.settings.stickers.suggest) { this.stickersHelper.checkEmoticon(''); } else { this.onMessageInput(); - } + } */ } if(this.messageInputField) { @@ -1063,18 +1064,6 @@ export default class ChatInput { //this.chat.log('messageInput entities', richValue, value, markdownEntities, caretPos); - if(this.stickersHelper && - rootScope.settings.stickers.suggest && - (this.chat.peerId > 0 || this.appChatsManager.hasRights(this.chat.peerId, 'send_stickers'))) { - let emoticon = ''; - const entity = entities[0]; - if(entity?._ === 'messageEntityEmoji' && entity.length === richValue.length && !entity.offset) { - emoticon = richValue; - } - - this.stickersHelper.checkEmoticon(emoticon); - } - if(this.canRedoFromHTML && !this.lockRedo && this.messageInput.innerHTML !== this.canRedoFromHTML) { this.canRedoFromHTML = ''; this.undoHistory.length = 0; @@ -1148,7 +1137,7 @@ export default class ChatInput { this.saveDraftDebounced(); } - this.checkAutocomplete(richValue, caretPos); + this.checkAutocomplete(richValue, caretPos, entities); this.updateSendBtn(); }; @@ -1221,25 +1210,31 @@ export default class ChatInput { } }; - private checkAutocomplete(value?: string, caretPos?: number) { + private checkAutocomplete(value?: string, caretPos?: number, entities?: MessageEntity[]) { //return; if(value === undefined) { - const r = getRichValueWithCaret(this.messageInputField.input, false); + const r = getRichValueWithCaret(this.messageInputField.input, true); value = r.value; caretPos = r.caretPos; + entities = r.entities; } if(caretPos === -1) { caretPos = value.length; } + + if(entities === undefined) { + const _value = RichTextProcessor.parseMarkdown(value, entities, true); + entities = RichTextProcessor.mergeEntities(entities, RichTextProcessor.parseEntities(_value)); + } + value = value.substr(0, caretPos); const matches = value.match(ChatInput.AUTO_COMPLETE_REG_EXP); if(!matches) { delete this.previousQuery; - //this.hideSuggestions(); - this.emojiHelper.toggle(true); + this.autocompleteHelperController.hideOtherHelpers(); return; } @@ -1248,6 +1243,17 @@ export default class ChatInput { } this.previousQuery = matches[0]; + + let foundHelper: AutocompleteHelper; + const entity = entities[0]; + + if(this.stickersHelper && + rootScope.settings.stickers.suggest && + (this.chat.peerId > 0 || this.appChatsManager.hasRights(this.chat.peerId, 'send_stickers')) && + entity?._ === 'messageEntityEmoji' && entity.length === value.length && !entity.offset) { + foundHelper = this.stickersHelper; + this.stickersHelper.checkEmoticon(value); + } else //let query = cleanSearchText(matches[2]); //const firstChar = matches[2][0]; @@ -1277,7 +1283,8 @@ export default class ChatInput { this.hideSuggestions() } } else */ if(!matches[1] && matches[2][0] === '/') { // commands - if(this.chat.peerId > 0) { + if(appUsersManager.isBot(this.chat.peerId)) { + foundHelper = this.commandsHelper; this.chat.appProfileManager.getProfileByPeerId(this.chat.peerId).then(full => { const botInfos: BotInfo.botInfo[] = [].concat(full.bot_info); const index = new SearchIndex(false, false); @@ -1293,22 +1300,22 @@ export default class ChatInput { const found = index.search(matches[2]); const filtered = Array.from(found).map(command => commands.get(command)); this.commandsHelper.render(filtered); - console.log('found commands', found, filtered); + // console.log('found commands', found, filtered); }); } } else { // emoji - if(value.match(/^\s*:(.+):\s*$/)) { - this.emojiHelper.toggle(true); - return; + if(!value.match(/^\s*:(.+):\s*$/)) { + foundHelper = this.emojiHelper; + this.appEmojiManager.getBothEmojiKeywords().then(() => { + const q = matches[2].replace(/^:/, ''); + const emojis = this.appEmojiManager.searchEmojis(q); + this.emojiHelper.render(emojis, matches[2][0] !== ':'); + //console.log(emojis); + }); } - - this.appEmojiManager.getBothEmojiKeywords().then(() => { - const q = matches[2].replace(/^:/, ''); - const emojis = this.appEmojiManager.searchEmojis(q); - this.emojiHelper.render(emojis, matches[2][0] !== ':'); - //console.log(emojis); - }); } + + this.autocompleteHelperController.hideOtherHelpers(foundHelper); } private onBtnSendClick = (e: Event) => { diff --git a/src/components/chat/search.ts b/src/components/chat/search.ts index 58a2cb0e..a4259807 100644 --- a/src/components/chat/search.ts +++ b/src/components/chat/search.ts @@ -37,7 +37,7 @@ export default class ChatSearch { private selectedIndex = 0; private setPeerPromise: Promise; - constructor(private topbar: ChatTopbar, private chat: Chat) { + constructor(private topbar: ChatTopbar, private chat: Chat, private query?: string) { this.element = document.createElement('div'); this.element.classList.add('sidebar-header', 'chat-search', 'chatlist-container'); @@ -128,6 +128,8 @@ export default class ChatSearch { this.topbar.container.parentElement.append(this.element); this.inputSearch.input.focus(); + + query && (this.inputSearch.inputField.value = query); } onDateClick = (e: MouseEvent) => { diff --git a/src/components/chat/stickersHelper.ts b/src/components/chat/stickersHelper.ts index 6f42353b..21acac4f 100644 --- a/src/components/chat/stickersHelper.ts +++ b/src/components/chat/stickersHelper.ts @@ -12,17 +12,23 @@ import { SuperStickerRenderer } from "../emoticonsDropdown/tabs/stickers"; import LazyLoadQueue from "../lazyLoadQueue"; import Scrollable from "../scrollable"; import AutocompleteHelper from "./autocompleteHelper"; +import AutocompleteHelperController from "./autocompleteHelperController"; export default class StickersHelper extends AutocompleteHelper { private scrollable: Scrollable; private superStickerRenderer: SuperStickerRenderer; private lazyLoadQueue: LazyLoadQueue; - private lastEmoticon = ''; - constructor(appendTo: HTMLElement) { - super(appendTo, 'xy', (target) => { - EmoticonsDropdown.onMediaClick({target}, true); - }, 'ArrowUp'); + constructor(appendTo: HTMLElement, controller: AutocompleteHelperController) { + super({ + appendTo, + controller, + listType: 'xy', + onSelect: (target) => { + EmoticonsDropdown.onMediaClick({target}, true); + }, + waitForKey: 'ArrowUp' + }); this.container.classList.add('stickers-helper'); @@ -34,26 +40,15 @@ export default class StickersHelper extends AutocompleteHelper { } public checkEmoticon(emoticon: string) { - if(this.lastEmoticon === emoticon) return; + const middleware = this.controller.getMiddleware(); - if(this.lastEmoticon && !emoticon) { - if(this.container) { - this.toggle(true); - } - } - - this.lastEmoticon = emoticon; if(this.lazyLoadQueue) { this.lazyLoadQueue.clear(); } - - if(!emoticon) { - return; - } appStickersManager.getStickersByEmoticon(emoticon) .then((stickers) => { - if(this.lastEmoticon !== emoticon) { + if(!middleware()) { return; } diff --git a/src/components/chat/topbar.ts b/src/components/chat/topbar.ts index e83bab8c..560eb843 100644 --- a/src/components/chat/topbar.ts +++ b/src/components/chat/topbar.ts @@ -19,14 +19,12 @@ import ButtonIcon from "../buttonIcon"; import ButtonMenuToggle from "../buttonMenuToggle"; import ChatAudio from "./audio"; import ChatPinnedMessage from "./pinnedMessage"; -import ChatSearch from "./search"; import { ButtonMenuItemOptions } from "../buttonMenu"; import ListenerSetter from "../../helpers/listenerSetter"; import appStateManager from "../../lib/appManagers/appStateManager"; import PopupDeleteDialog from "../popups/deleteDialog"; import appNavigationController from "../appNavigationController"; import { LEFT_COLUMN_ACTIVE_CLASSNAME } from "../sidebarLeft"; -import AppPrivateSearchTab from "../sidebarRight/tabs/search"; import PeerTitle from "../peerTitle"; import { i18n } from "../../lib/langPack"; import findUpClassName from "../../helpers/dom/findUpClassName"; @@ -35,30 +33,30 @@ import { cancelEvent } from "../../helpers/dom/cancelEvent"; import { attachClickEvent } from "../../helpers/dom/clickEvent"; export default class ChatTopbar { - container: HTMLDivElement; - btnBack: HTMLButtonElement; - chatInfo: HTMLDivElement; - avatarElement: AvatarElement; - title: HTMLDivElement; - subtitle: HTMLDivElement; - chatUtils: HTMLDivElement; - btnJoin: HTMLButtonElement; - btnPinned: HTMLButtonElement; - btnMute: HTMLButtonElement; - btnSearch: HTMLButtonElement; - btnMore: HTMLButtonElement; + public container: HTMLDivElement; + private btnBack: HTMLButtonElement; + private chatInfo: HTMLDivElement; + private avatarElement: AvatarElement; + private title: HTMLDivElement; + private subtitle: HTMLDivElement; + private chatUtils: HTMLDivElement; + private btnJoin: HTMLButtonElement; + private btnPinned: HTMLButtonElement; + private btnMute: HTMLButtonElement; + private btnSearch: HTMLButtonElement; + private btnMore: HTMLButtonElement; - public chatAudio: ChatAudio; + private chatAudio: ChatAudio; public pinnedMessage: ChatPinnedMessage; private setUtilsRAF: number; public peerId: number; - public wasPeerId: number; + private wasPeerId: number; private setPeerStatusInterval: number; public listenerSetter: ListenerSetter; - public menuButtons: (ButtonMenuItemOptions & {verify: () => boolean})[] = []; + private menuButtons: (ButtonMenuItemOptions & {verify: () => boolean})[] = []; constructor(private chat: Chat, private appSidebarRight: AppSidebarRight, private appMessagesManager: AppMessagesManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager, private appNotificationsManager: AppNotificationsManager) { this.listenerSetter = new ListenerSetter(); @@ -189,7 +187,7 @@ export default class ChatTopbar { icon: 'search', text: 'Search', onClick: () => { - new ChatSearch(this, this.chat); + this.chat.initSearch() }, verify: () => mediaSizes.isMobile }, /* { @@ -237,14 +235,7 @@ export default class ChatTopbar { this.btnSearch = ButtonIcon('search'); attachClickEvent(this.btnSearch, (e) => { cancelEvent(e); - if(this.peerId) { - let tab = this.appSidebarRight.getTab(AppPrivateSearchTab); - if(!tab) { - tab = new AppPrivateSearchTab(this.appSidebarRight); - } - - tab.open(this.peerId, this.chat.threadId, this.chat.bubbles.onDatePick); - } + this.chat.initSearch(); }, {listenerSetter: this.listenerSetter}); } diff --git a/src/components/sidebarRight/tabs/search.ts b/src/components/sidebarRight/tabs/search.ts index df07a2d0..b32ec5ca 100644 --- a/src/components/sidebarRight/tabs/search.ts +++ b/src/components/sidebarRight/tabs/search.ts @@ -42,13 +42,15 @@ export default class AppPrivateSearchTab extends SliderSuperTab { }); } - open(peerId: number, threadId?: number, onDatePick?: AppPrivateSearchTab['onDatePick']) { + open(peerId: number, threadId?: number, onDatePick?: AppPrivateSearchTab['onDatePick'], query?: string) { const ret = super.open(); if(this.init) { this.init(); this.init = null; } + query && (this.inputSearch.inputField.value = query); + if(this.peerId !== 0) { this.appSearch.beginSearch(this.peerId, this.threadId); return ret; @@ -64,7 +66,7 @@ export default class AppPrivateSearchTab extends SliderSuperTab { new PopupDatePicker(new Date(), this.onDatePick).show(); }); } - + appSidebarRight.toggleSidebar(true); return ret; } diff --git a/src/layer.d.ts b/src/layer.d.ts index b4b5a5f5..e3443aff 100644 --- a/src/layer.d.ts +++ b/src/layer.d.ts @@ -4049,7 +4049,8 @@ export namespace MessageEntity { export type messageEntityBotCommand = { _: 'messageEntityBotCommand', offset: number, - length: number + length: number, + unsafe?: boolean }; export type messageEntityUrl = { diff --git a/src/lib/appManagers/appImManager.ts b/src/lib/appManagers/appImManager.ts index 1926bb67..11684de1 100644 --- a/src/lib/appManagers/appImManager.ts +++ b/src/lib/appManagers/appImManager.ts @@ -243,12 +243,47 @@ export class AppImManager { return false; }; - } - private onHashChange = () => { - const hash = location.hash; - const splitted = hash.split('?'); + (window as any).execBotCommand = (element: HTMLAnchorElement, e: Event) => { + cancelEvent(null); + + const href = element.href; + const params = this.parseUriParams(href); + if(!params) { + return; + } + const {command, bot} = params; + + /* const promise = bot ? this.openUsername(bot).then(() => this.chat.peerId) : Promise.resolve(this.chat.peerId); + promise.then(peerId => { + appMessagesManager.sendText(peerId, '/' + command); + }); */ + + appMessagesManager.sendText(this.chat.peerId, '/' + command + (bot ? '@' + bot : '')); + + //console.log(command, bot); + + return false; + }; + + (window as any).searchByHashtag = (element: HTMLAnchorElement, e: Event) => { + cancelEvent(null); + + const href = element.href; + const params = this.parseUriParams(href); + if(!params) { + return; + } + + const {hashtag} = params; + this.chat.initSearch('#' + hashtag + ' '); + + return false; + }; + } + + private parseUriParams(uri: string, splitted = uri.split('?')) { if(!splitted[1]) { return; } @@ -258,6 +293,15 @@ export class AppImManager { params[item.split('=')[0]] = decodeURIComponent(item.split('=')[1]); }); + return params; + } + + private onHashChange = () => { + const hash = location.hash; + const splitted = hash.split('?'); + + const params = this.parseUriParams(hash, splitted); + this.log('hashchange', hash, splitted[0], params); switch(splitted[0]) { diff --git a/src/lib/appManagers/appMessagesManager.ts b/src/lib/appManagers/appMessagesManager.ts index a4624a9b..a6149e04 100644 --- a/src/lib/appManagers/appMessagesManager.ts +++ b/src/lib/appManagers/appMessagesManager.ts @@ -426,6 +426,7 @@ export class AppMessagesManager { let entities = options.entities || []; if(!options.viaBotId) { text = RichTextProcessor.parseMarkdown(text, entities); + entities = RichTextProcessor.mergeEntities(entities, RichTextProcessor.parseEntities(text)); } let sendEntites = this.getInputEntities(entities); @@ -2549,6 +2550,7 @@ export class AppMessagesManager { let entities = RichTextProcessor.parseEntities(text.replace(/\n/g, ' ')); if(highlightWord) { + highlightWord = highlightWord.trim(); if(!entities) entities = []; let found = false; let match: any; diff --git a/src/lib/appManagers/appUsersManager.ts b/src/lib/appManagers/appUsersManager.ts index e4f46b44..bb51510b 100644 --- a/src/lib/appManagers/appUsersManager.ts +++ b/src/lib/appManagers/appUsersManager.ts @@ -352,6 +352,10 @@ export class AppUsersManager { changedTitle = true; } + /* if(user.pFlags.bot && user.bot_info_version !== oldUser.bot_info_version) { + + } */ + safeReplaceObject(oldUser, user); rootScope.broadcast('user_update', userId); } diff --git a/src/lib/richtextprocessor.ts b/src/lib/richtextprocessor.ts index c91718e1..24a4b4dc 100644 --- a/src/lib/richtextprocessor.ts +++ b/src/lib/richtextprocessor.ts @@ -214,11 +214,12 @@ namespace RichTextProcessor { offset: matchIndex + (match[10] ? match[10].length : 0), length: match[11].length }); - } else if(match[12]) { // Bot command + } else if(match[13]) { // Bot command entities.push({ _: 'messageEntityBotCommand', offset: matchIndex + (match[11] ? match[11].length : 0), - length: 1 + match[12].length + (match[13] ? 1 + match[13].length : 0) + length: 1 + match[13].length + (match[14] ? 1 + match[14].length : 0), + unsafe: true }); } @@ -417,7 +418,7 @@ namespace RichTextProcessor { contextHashtag?: string }> = {}) { - if(!text || !text.length) { + if(!text) { return ''; } @@ -512,7 +513,7 @@ namespace RichTextProcessor { } case 'messageEntityBotCommand': { - if(!(options.noLinks || options.noCommands || contextExternal)) { + if(!(options.noLinks || options.noCommands || contextExternal) && !entity.unsafe) { const entityText = text.substr(entity.offset, entity.length); let command = entityText.substr(1); let bot: string | boolean; @@ -524,7 +525,7 @@ namespace RichTextProcessor { bot = options.fromBot; } - insertPart(entity, ``, ``); + insertPart(entity, ``, ``); } break; @@ -617,7 +618,7 @@ namespace RichTextProcessor { if(contextUrl) { const entityText = text.substr(entity.offset, entity.length); const hashtag = entityText.substr(1); - insertPart(entity, ``, ''); + insertPart(entity, ``, ''); } break; diff --git a/src/scripts/in/schema_additional_params.json b/src/scripts/in/schema_additional_params.json index a27c7637..e46e2da2 100644 --- a/src/scripts/in/schema_additional_params.json +++ b/src/scripts/in/schema_additional_params.json @@ -101,6 +101,11 @@ {"name": "length", "type": "number"} ], "type": "MessageEntity" +}, { + "predicate": "messageEntityBotCommand", + "params": [ + {"name": "unsafe", "type": "boolean"} + ] }, { "predicate": "user", "params": [