Autocomplete bot commands
Search by hashtags
This commit is contained in:
parent
072494d668
commit
d8be5fc0ba
@ -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;
|
||||
|
@ -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');
|
||||
});
|
||||
|
@ -1,21 +1,33 @@
|
||||
import { getMiddleware } from "../../helpers/middleware";
|
||||
import AutocompleteHelper from "./autocompleteHelper";
|
||||
|
||||
export default class AutocompleteHelperController {
|
||||
private helpers: Set<AutocompleteHelper> = new Set();
|
||||
private middleware = getMiddleware();
|
||||
/* private tempId = 0;
|
||||
|
||||
public addHelpers(helpers: AutocompleteHelper[]) {
|
||||
for(const helper of helpers) {
|
||||
public incrementToggleCount() {
|
||||
return ++this.tempId;
|
||||
}
|
||||
|
||||
public getToggleCount() {
|
||||
return this.tempId;
|
||||
} */
|
||||
|
||||
public getMiddleware() {
|
||||
this.middleware.clean();
|
||||
return this.middleware.get();
|
||||
}
|
||||
|
||||
public addHelper(helper: AutocompleteHelper) {
|
||||
this.helpers.add(helper);
|
||||
}
|
||||
}
|
||||
|
||||
public toggleHelper(helper: AutocompleteHelper, hide?: boolean) {
|
||||
public hideOtherHelpers(helper?: AutocompleteHelper) {
|
||||
this.helpers.forEach(h => {
|
||||
if(h !== helper) {
|
||||
helper.toggle(true);
|
||||
h.toggle(true);
|
||||
}
|
||||
});
|
||||
|
||||
helper.toggle(hide);
|
||||
}
|
||||
}
|
||||
|
@ -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<boolean>;
|
||||
private getHistoryBottomPromise: Promise<boolean>;
|
||||
@ -88,11 +88,11 @@ export default class ChatBubbles {
|
||||
public peerId = 0;
|
||||
//public messagesCount: number = -1;
|
||||
|
||||
public unreadOut = new Set<number>();
|
||||
private unreadOut = new Set<number>();
|
||||
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<number> = new Set();
|
||||
private readPromise: Promise<void>;
|
||||
|
||||
public bubbleGroups: BubbleGroups;
|
||||
private bubbleGroups: BubbleGroups;
|
||||
|
||||
private preloader: ProgressivePreloader = null;
|
||||
|
||||
private loadedTopTimes = 0;
|
||||
private loadedBottomTimes = 0;
|
||||
|
||||
public messagesQueuePromise: Promise<void> = null;
|
||||
private messagesQueuePromise: Promise<void> = null;
|
||||
private messagesQueue: {message: any, bubble: HTMLDivElement, reverse: boolean, promises: Promise<void>[]}[] = [];
|
||||
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<void>;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
|
@ -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) => {
|
||||
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');
|
||||
|
@ -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<string>(false, false);
|
||||
@ -1293,15 +1300,12 @@ 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);
|
||||
@ -1311,6 +1315,9 @@ export default class ChatInput {
|
||||
}
|
||||
}
|
||||
|
||||
this.autocompleteHelperController.hideOtherHelpers(foundHelper);
|
||||
}
|
||||
|
||||
private onBtnSendClick = (e: Event) => {
|
||||
cancelEvent(e);
|
||||
|
||||
|
@ -37,7 +37,7 @@ export default class ChatSearch {
|
||||
private selectedIndex = 0;
|
||||
private setPeerPromise: Promise<any>;
|
||||
|
||||
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) => {
|
||||
|
@ -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) => {
|
||||
constructor(appendTo: HTMLElement, controller: AutocompleteHelperController) {
|
||||
super({
|
||||
appendTo,
|
||||
controller,
|
||||
listType: 'xy',
|
||||
onSelect: (target) => {
|
||||
EmoticonsDropdown.onMediaClick({target}, true);
|
||||
}, 'ArrowUp');
|
||||
},
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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});
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
3
src/layer.d.ts
vendored
3
src/layer.d.ts
vendored
@ -4049,7 +4049,8 @@ export namespace MessageEntity {
|
||||
export type messageEntityBotCommand = {
|
||||
_: 'messageEntityBotCommand',
|
||||
offset: number,
|
||||
length: number
|
||||
length: number,
|
||||
unsafe?: boolean
|
||||
};
|
||||
|
||||
export type messageEntityUrl = {
|
||||
|
@ -243,12 +243,47 @@ export class AppImManager {
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
(window as any).execBotCommand = (element: HTMLAnchorElement, e: Event) => {
|
||||
cancelEvent(null);
|
||||
|
||||
const href = element.href;
|
||||
const params = this.parseUriParams(href);
|
||||
if(!params) {
|
||||
return;
|
||||
}
|
||||
|
||||
private onHashChange = () => {
|
||||
const hash = location.hash;
|
||||
const splitted = hash.split('?');
|
||||
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]) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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, `<a href="${encodeEntities('tg://bot_command?command=' + encodeURIComponent(command) + (bot ? '&bot=' + encodeURIComponent(bot) : ''))}">`, `</a>`);
|
||||
insertPart(entity, `<a href="${encodeEntities('tg://bot_command?command=' + encodeURIComponent(command) + (bot ? '&bot=' + encodeURIComponent(bot) : ''))}" onclick="execBotCommand(this)">`, `</a>`);
|
||||
}
|
||||
|
||||
break;
|
||||
@ -617,7 +618,7 @@ namespace RichTextProcessor {
|
||||
if(contextUrl) {
|
||||
const entityText = text.substr(entity.offset, entity.length);
|
||||
const hashtag = entityText.substr(1);
|
||||
insertPart(entity, `<a class="anchor-hashtag" href="${contextUrl.replace('{1}', encodeURIComponent(hashtag))}"${contextExternal ? ' target="_blank" rel="noopener noreferrer"' : ''}>`, '</a>');
|
||||
insertPart(entity, `<a class="anchor-hashtag" href="${contextUrl.replace('{1}', encodeURIComponent(hashtag))}"${contextExternal ? ' target="_blank" rel="noopener noreferrer"' : ' onclick="searchByHashtag(this)"'}>`, '</a>');
|
||||
}
|
||||
|
||||
break;
|
||||
|
@ -101,6 +101,11 @@
|
||||
{"name": "length", "type": "number"}
|
||||
],
|
||||
"type": "MessageEntity"
|
||||
}, {
|
||||
"predicate": "messageEntityBotCommand",
|
||||
"params": [
|
||||
{"name": "unsafe", "type": "boolean"}
|
||||
]
|
||||
}, {
|
||||
"predicate": "user",
|
||||
"params": [
|
||||
|
Loading…
Reference in New Issue
Block a user