diff --git a/src/components/chat/autocompleteHelper.ts b/src/components/chat/autocompleteHelper.ts index a335c1d9..bfe687e5 100644 --- a/src/components/chat/autocompleteHelper.ts +++ b/src/components/chat/autocompleteHelper.ts @@ -86,12 +86,14 @@ export default class AutocompleteHelper extends EventListenerBase<{ this.addEventListener('visible', this.onVisible); } - public toggle(hide?: boolean) { + public toggle(hide?: boolean, fromController = false) { if(this.init) { return; } - hide = hide === undefined ? this.container.classList.contains('is-visible') && !this.container.classList.contains('backwards') : hide; + if(hide === undefined) { + hide = this.container.classList.contains('is-visible') && !this.container.classList.contains('backwards'); + } if(this.hidden === hide) { if(!hide && this.resetTarget) { @@ -103,10 +105,10 @@ export default class AutocompleteHelper extends EventListenerBase<{ this.hidden = hide; - if(!this.hidden) { + if(!hide) { this.controller.hideOtherHelpers(this); this.dispatchEvent('visible'); // fire it before so target will be set - } else { + } else if(!fromController) { this.controller.hideOtherHelpers(); } diff --git a/src/components/chat/autocompleteHelperController.ts b/src/components/chat/autocompleteHelperController.ts index 52cf10da..67f2e43e 100644 --- a/src/components/chat/autocompleteHelperController.ts +++ b/src/components/chat/autocompleteHelperController.ts @@ -29,11 +29,15 @@ export default class AutocompleteHelperController { this.helpers.add(helper); } - public hideOtherHelpers(helper?: AutocompleteHelper) { - this.helpers.forEach(h => { - if(h !== helper) { - h.toggle(true); + public hideOtherHelpers(preserveHelper?: AutocompleteHelper) { + this.helpers.forEach(helper => { + if(helper !== preserveHelper) { + helper.toggle(true, true); } }); + + if(!preserveHelper) { + this.middleware.clean(); + } } } diff --git a/src/components/chat/commandsHelper.ts b/src/components/chat/commandsHelper.ts index d444be2d..c02b06c8 100644 --- a/src/components/chat/commandsHelper.ts +++ b/src/components/chat/commandsHelper.ts @@ -5,11 +5,19 @@ */ import type ChatInput from "./input"; +import type { AppProfileManager } from "../../lib/appManagers/appProfileManager"; +import type { AppUsersManager } from "../../lib/appManagers/appUsersManager"; +import type { BotInfo } from "../../layer"; import AutocompleteHelperController from "./autocompleteHelperController"; import AutocompletePeerHelper from "./autocompletePeerHelper"; +import SearchIndex from "../../lib/searchIndex"; export default class CommandsHelper extends AutocompletePeerHelper { - constructor(appendTo: HTMLElement, controller: AutocompleteHelperController, private chatInput: ChatInput) { + constructor(appendTo: HTMLElement, + controller: AutocompleteHelperController, + chatInput: ChatInput, + private appProfileManager: AppProfileManager, + private appUsersManager: AppUsersManager) { super(appendTo, controller, 'commands-helper', @@ -20,4 +28,41 @@ export default class CommandsHelper extends AutocompletePeerHelper { } ); } + + public checkQuery(query: string, peerId: number) { + if(!this.appUsersManager.isBot(peerId)) { + return false; + } + + const middleware = this.controller.getMiddleware(); + this.appProfileManager.getProfileByPeerId(peerId).then(full => { + if(!middleware()) { + return; + } + + const botInfos: BotInfo.botInfo[] = [].concat(full.bot_info); + const index = new SearchIndex(false, false); + + const commands: Map = new Map(); + botInfos.forEach(botInfo => { + botInfo.commands.forEach(botCommand => { + const c = '/' + botCommand.command; + commands.set(botCommand.command, { + peerId: botInfo.user_id, + name: c, + description: botCommand.description + }); + + index.indexObject(botCommand.command, c); + }); + }); + + const found = index.search(query); + const filtered = Array.from(found).map(command => commands.get(command)); + this.render(filtered); + // console.log('found commands', found, filtered); + }); + + return true; + } } diff --git a/src/components/chat/emojiHelper.ts b/src/components/chat/emojiHelper.ts index f361774a..4bba51a9 100644 --- a/src/components/chat/emojiHelper.ts +++ b/src/components/chat/emojiHelper.ts @@ -5,6 +5,7 @@ */ import type ChatInput from "./input"; +import type { AppEmojiManager } from "../../lib/appManagers/appEmojiManager"; import { appendEmoji, getEmojiFromElement } from "../emoticonsDropdown/tabs/emoji"; import { ScrollableX } from "../scrollable"; import AutocompleteHelper from "./autocompleteHelper"; @@ -13,13 +14,16 @@ import AutocompleteHelperController from "./autocompleteHelperController"; export default class EmojiHelper extends AutocompleteHelper { private scrollable: ScrollableX; - constructor(appendTo: HTMLElement, controller: AutocompleteHelperController, private chatInput: ChatInput) { + constructor(appendTo: HTMLElement, + controller: AutocompleteHelperController, + chatInput: ChatInput, + private appEmojiManager: AppEmojiManager) { super({ appendTo, controller, listType: 'x', onSelect: (target) => { - this.chatInput.onEmojiSelected(getEmojiFromElement(target as any), true); + chatInput.onEmojiSelected(getEmojiFromElement(target as any), true); } }); @@ -50,6 +54,8 @@ export default class EmojiHelper extends AutocompleteHelper { this.init(); this.init = null; } + + emojis = emojis.slice(0, 80); if(emojis.length) { this.list.innerHTML = ''; @@ -65,4 +71,18 @@ export default class EmojiHelper extends AutocompleteHelper { this.container.style.width = (3 * 2) + (emojis.length * 44) + 'px'; }); */ } + + public checkQuery(query: string, firstChar: string) { + const middleware = this.controller.getMiddleware(); + this.appEmojiManager.getBothEmojiKeywords().then(() => { + if(!middleware()) { + return; + } + + const q = query.replace(/^:/, ''); + const emojis = this.appEmojiManager.searchEmojis(q); + this.render(emojis, firstChar !== ':'); + //console.log(emojis); + }); + } } diff --git a/src/components/chat/input.ts b/src/components/chat/input.ts index e0bfb2ea..0b632fa6 100644 --- a/src/components/chat/input.ts +++ b/src/components/chat/input.ts @@ -60,11 +60,9 @@ import { MarkdownType, markdownTags } from '../../helpers/dom/getRichElementValu import getRichValueWithCaret from '../../helpers/dom/getRichValueWithCaret'; import EmojiHelper from './emojiHelper'; 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'; import MentionsHelper from './mentionsHelper'; const RECORD_MIN_TIME = 500; @@ -372,9 +370,9 @@ export default class ChatInput { this.rowsWrapper.append(this.replyElements.container); this.autocompleteHelperController = new AutocompleteHelperController(); 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.mentionsHelper = new MentionsHelper(this.rowsWrapper, this.autocompleteHelperController, this); + this.emojiHelper = new EmojiHelper(this.rowsWrapper, this.autocompleteHelperController, this, this.appEmojiManager); + this.commandsHelper = new CommandsHelper(this.rowsWrapper, this.autocompleteHelperController, this, this.chat.appProfileManager, this.chat.appUsersManager); + this.mentionsHelper = new MentionsHelper(this.rowsWrapper, this.autocompleteHelperController, this, this.chat.appProfileManager, this.chat.appUsersManager); this.rowsWrapper.append(this.newMessageWrapper); this.btnCancelRecord = ButtonIcon('delete danger btn-circle z-depth-1 btn-record-cancel'); @@ -1258,61 +1256,18 @@ export default class ChatInput { //console.log('autocomplete matches', matches); if(firstChar === '@') { // mentions - const trimmed = query.trim(); // check that there is no whitespace - if(this.chat.peerId < 0 && query.length === trimmed.length) { + const topMsgId = this.chat.threadId ? this.appMessagesManager.getServerMessageId(this.chat.threadId) : undefined; + if(this.mentionsHelper.checkQuery(query, this.chat.peerId, topMsgId)) { foundHelper = this.mentionsHelper; - const topMsgId = this.chat.threadId ? this.appMessagesManager.getServerMessageId(this.chat.threadId) : undefined; - this.chat.appProfileManager.getMentions(-this.chat.peerId, trimmed, topMsgId).then(peerIds => { - const username = trimmed.slice(1).toLowerCase(); - this.mentionsHelper.render(peerIds.map(peerId => { - const user = this.chat.appUsersManager.getUser(peerId); - if(user.username && user.username.toLowerCase() === username) { // hide full matched suggestion - return; - } - - return { - peerId, - description: user.username ? '@' + user.username : undefined - }; - }).filter(Boolean)); - }); } } else if(!matches[1] && firstChar === '/') { // commands - if(appUsersManager.isBot(this.chat.peerId)) { + if(this.commandsHelper.checkQuery(query, 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(true, false); - - const commands: Map = new Map(); - botInfos.forEach(botInfo => { - botInfo.commands.forEach(botCommand => { - const c = '/' + botCommand.command; - commands.set(botCommand.command, { - peerId: botInfo.user_id, - name: c, - description: botCommand.description - }); - - index.indexObject(botCommand.command, c); - }); - }); - - const found = index.search(query); - const filtered = Array.from(found).map(command => commands.get(command)); - this.commandsHelper.render(filtered); - // console.log('found commands', found, filtered); - }); } } else if(rootScope.settings.emoji.suggest) { // emoji if(!value.match(/^\s*:(.+):\s*$/)) { foundHelper = this.emojiHelper; - this.appEmojiManager.getBothEmojiKeywords().then(() => { - const q = query.replace(/^:/, ''); - const emojis = this.appEmojiManager.searchEmojis(q); - this.emojiHelper.render(emojis, firstChar !== ':'); - //console.log(emojis); - }); + this.emojiHelper.checkQuery(query, firstChar); } } diff --git a/src/components/chat/mentionsHelper.ts b/src/components/chat/mentionsHelper.ts index ba0be516..7a523322 100644 --- a/src/components/chat/mentionsHelper.ts +++ b/src/components/chat/mentionsHelper.ts @@ -6,12 +6,17 @@ import type ChatInput from "./input"; import type { MessageEntity } from "../../layer"; +import type { AppProfileManager } from "../../lib/appManagers/appProfileManager"; +import type { AppUsersManager } from "../../lib/appManagers/appUsersManager"; import AutocompleteHelperController from "./autocompleteHelperController"; import AutocompletePeerHelper from "./autocompletePeerHelper"; -import appUsersManager from "../../lib/appManagers/appUsersManager"; export default class MentionsHelper extends AutocompletePeerHelper { - constructor(appendTo: HTMLElement, controller: AutocompleteHelperController, private chatInput: ChatInput) { + constructor(appendTo: HTMLElement, + controller: AutocompleteHelperController, + chatInput: ChatInput, + private appProfileManager: AppProfileManager, + private appUsersManager: AppUsersManager) { super(appendTo, controller, 'mentions-helper', @@ -35,4 +40,26 @@ export default class MentionsHelper extends AutocompletePeerHelper { } ); } + + public checkQuery(query: string, peerId: number, topMsgId: number) { + const trimmed = query.trim(); // check that there is no whitespace + if(peerId > 0 || query.length !== trimmed.length) return false; + + this.appProfileManager.getMentions(-peerId, trimmed, topMsgId).then(peerIds => { + const username = trimmed.slice(1).toLowerCase(); + this.render(peerIds.map(peerId => { + const user = this.appUsersManager.getUser(peerId); + if(user.username && user.username.toLowerCase() === username) { // hide full matched suggestion + return; + } + + return { + peerId, + description: user.username ? '@' + user.username : undefined + }; + }).filter(Boolean)); + }); + + return true; + } }