Fix commands entities on sending
Fix emojis without FE0F for Apple's devices Shortened autocomplete helpers
This commit is contained in:
parent
58e8c41adb
commit
b2e3683f93
@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
import { getMiddleware } from "../../helpers/middleware";
|
import { getMiddleware } from "../../helpers/middleware";
|
||||||
import AutocompleteHelper from "./autocompleteHelper";
|
import AutocompleteHelper from "./autocompleteHelper";
|
||||||
|
|
||||||
|
114
src/components/chat/autocompletePeerHelper.ts
Normal file
114
src/components/chat/autocompletePeerHelper.ts
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
/*
|
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
import RichTextProcessor from "../../lib/richtextprocessor";
|
||||||
|
import AvatarElement from "../avatar";
|
||||||
|
import PeerTitle from "../peerTitle";
|
||||||
|
import Scrollable from "../scrollable";
|
||||||
|
import AutocompleteHelper from "./autocompleteHelper";
|
||||||
|
import AutocompleteHelperController from "./autocompleteHelperController";
|
||||||
|
|
||||||
|
export default class AutocompletePeerHelper extends AutocompleteHelper {
|
||||||
|
protected static BASE_CLASS = 'autocomplete-peer-helper';
|
||||||
|
protected static BASE_CLASS_LIST_ELEMENT = AutocompletePeerHelper.BASE_CLASS + '-list-element';
|
||||||
|
private scrollable: Scrollable;
|
||||||
|
|
||||||
|
constructor(appendTo: HTMLElement, controller: AutocompleteHelperController, protected className: string, onSelect: (target: Element) => boolean | void) {
|
||||||
|
super({
|
||||||
|
appendTo,
|
||||||
|
controller,
|
||||||
|
listType: 'y',
|
||||||
|
onSelect
|
||||||
|
});
|
||||||
|
|
||||||
|
this.container.classList.add(AutocompletePeerHelper.BASE_CLASS, className);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected init() {
|
||||||
|
this.list = document.createElement('div');
|
||||||
|
this.list.classList.add(AutocompletePeerHelper.BASE_CLASS + '-list');
|
||||||
|
|
||||||
|
this.container.append(this.list);
|
||||||
|
|
||||||
|
this.scrollable = new Scrollable(this.container);
|
||||||
|
|
||||||
|
this.addEventListener('visible', () => {
|
||||||
|
setTimeout(() => { // it is not rendered yet
|
||||||
|
this.scrollable.container.scrollTop = 0;
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public render(data: {peerId: number, name?: string, description?: string}[]) {
|
||||||
|
if(this.init) {
|
||||||
|
if(!data.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
this.init = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(data.length) {
|
||||||
|
this.list.innerHTML = '';
|
||||||
|
data.forEach(d => {
|
||||||
|
const div = AutocompletePeerHelper.listElement({
|
||||||
|
className: this.className,
|
||||||
|
peerId: d.peerId,
|
||||||
|
name: d.name,
|
||||||
|
description: d.description
|
||||||
|
});
|
||||||
|
|
||||||
|
this.list.append(div);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggle(!data.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static listElement(options: {
|
||||||
|
className: string,
|
||||||
|
peerId: number,
|
||||||
|
name?: string,
|
||||||
|
description?: string
|
||||||
|
}) {
|
||||||
|
const BASE = AutocompletePeerHelper.BASE_CLASS_LIST_ELEMENT;
|
||||||
|
options.className += '-list-element';
|
||||||
|
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.classList.add(BASE, options.className);
|
||||||
|
div.dataset.peerId = '' + options.peerId;
|
||||||
|
|
||||||
|
const avatar = new AvatarElement();
|
||||||
|
avatar.classList.add('avatar-30');
|
||||||
|
avatar.setAttribute('dialog', '0');
|
||||||
|
avatar.setAttribute('peer', '' + options.peerId);
|
||||||
|
|
||||||
|
const name = document.createElement('div');
|
||||||
|
name.classList.add(BASE + '-name', options.className + '-name');
|
||||||
|
if(!options.name) {
|
||||||
|
name.append(new PeerTitle({
|
||||||
|
peerId: options.peerId,
|
||||||
|
dialog: false,
|
||||||
|
onlyFirstName: false,
|
||||||
|
plainText: false
|
||||||
|
}).element);
|
||||||
|
} else {
|
||||||
|
name.innerHTML = RichTextProcessor.wrapEmojiText(options.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
div.append(avatar, name);
|
||||||
|
|
||||||
|
if(options.description) {
|
||||||
|
const description = document.createElement('div');
|
||||||
|
description.classList.add(BASE + '-description', options.className + '-description');
|
||||||
|
description.innerHTML = RichTextProcessor.wrapEmojiText(options.description);
|
||||||
|
div.append(description);
|
||||||
|
}
|
||||||
|
|
||||||
|
return div;
|
||||||
|
}
|
||||||
|
}
|
@ -143,6 +143,10 @@ export default class ChatBubbles {
|
|||||||
private fetchNewPromise: Promise<void>;
|
private fetchNewPromise: Promise<void>;
|
||||||
private historyStorage: HistoryStorage;
|
private historyStorage: HistoryStorage;
|
||||||
|
|
||||||
|
private passEntities: Partial<{
|
||||||
|
[_ in MessageEntity['_']]: boolean
|
||||||
|
}> = {};
|
||||||
|
|
||||||
constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appStickersManager: AppStickersManager, private appUsersManager: AppUsersManager, private appInlineBotsManager: AppInlineBotsManager, private appPhotosManager: AppPhotosManager, private appDocsManager: AppDocsManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager, private storage: typeof sessionStorage) {
|
constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appStickersManager: AppStickersManager, private appUsersManager: AppUsersManager, private appInlineBotsManager: AppInlineBotsManager, private appPhotosManager: AppPhotosManager, private appDocsManager: AppDocsManager, private appPeersManager: AppPeersManager, private appChatsManager: AppChatsManager, private storage: typeof sessionStorage) {
|
||||||
//this.chat.log.error('Bubbles construction');
|
//this.chat.log.error('Bubbles construction');
|
||||||
|
|
||||||
@ -1544,6 +1548,10 @@ export default class ChatBubbles {
|
|||||||
|
|
||||||
this.peerId = peerId;
|
this.peerId = peerId;
|
||||||
this.replyFollowHistory.length = 0;
|
this.replyFollowHistory.length = 0;
|
||||||
|
|
||||||
|
this.passEntities = {
|
||||||
|
messageEntityBotCommand: this.appPeersManager.isAnyGroup(this.peerId) || this.appUsersManager.isBot(this.peerId)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if(DEBUG) {
|
if(DEBUG) {
|
||||||
@ -2029,7 +2037,8 @@ export default class ChatBubbles {
|
|||||||
entities: totalEntities
|
entities: totalEntities
|
||||||
}); */
|
}); */
|
||||||
let richText = RichTextProcessor.wrapRichText(messageMessage, {
|
let richText = RichTextProcessor.wrapRichText(messageMessage, {
|
||||||
entities: totalEntities
|
entities: totalEntities,
|
||||||
|
passEntities: this.passEntities
|
||||||
});
|
});
|
||||||
|
|
||||||
let canHaveTail = true;
|
let canHaveTail = true;
|
||||||
|
@ -1,78 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
import type ChatInput from "./input";
|
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";
|
import AutocompleteHelperController from "./autocompleteHelperController";
|
||||||
|
import AutocompletePeerHelper from "./autocompletePeerHelper";
|
||||||
|
|
||||||
export default class CommandsHelper extends AutocompleteHelper {
|
export default class CommandsHelper extends AutocompletePeerHelper {
|
||||||
private scrollable: Scrollable;
|
|
||||||
|
|
||||||
constructor(appendTo: HTMLElement, controller: AutocompleteHelperController, private chatInput: ChatInput) {
|
constructor(appendTo: HTMLElement, controller: AutocompleteHelperController, private chatInput: ChatInput) {
|
||||||
super({
|
super(appendTo,
|
||||||
appendTo,
|
|
||||||
controller,
|
controller,
|
||||||
listType: 'y',
|
'commands-helper',
|
||||||
onSelect: (target) => {
|
(target) => {
|
||||||
const command = target.querySelector('.commands-helper-command-name').innerHTML;
|
const innerHTML = target.querySelector(`.${AutocompletePeerHelper.BASE_CLASS_LIST_ELEMENT}-name`).innerHTML;
|
||||||
chatInput.messageInput.innerHTML = command;
|
chatInput.messageInput.innerHTML = innerHTML;
|
||||||
chatInput.sendMessage();
|
chatInput.sendMessage();
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|
||||||
this.container.classList.add('commands-helper');
|
|
||||||
}
|
|
||||||
|
|
||||||
protected init() {
|
|
||||||
this.list = document.createElement('div');
|
|
||||||
this.list.classList.add('commands-helper-commands');
|
|
||||||
|
|
||||||
this.container.append(this.list);
|
|
||||||
|
|
||||||
this.scrollable = new Scrollable(this.container);
|
|
||||||
|
|
||||||
this.addEventListener('visible', () => {
|
|
||||||
setTimeout(() => { // it is not rendered yet
|
|
||||||
this.scrollable.container.scrollTop = 0;
|
|
||||||
}, 0);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public render(commands: {userId: number, command: BotCommand}[]) {
|
|
||||||
if(this.init) {
|
|
||||||
if(!commands.length) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.init();
|
|
||||||
this.init = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(commands.length) {
|
|
||||||
this.list.innerHTML = '';
|
|
||||||
commands.forEach(command => {
|
|
||||||
const div = document.createElement('div');
|
|
||||||
div.classList.add('commands-helper-command');
|
|
||||||
|
|
||||||
const avatar = new AvatarElement();
|
|
||||||
avatar.classList.add('avatar-30');
|
|
||||||
avatar.setAttribute('dialog', '0');
|
|
||||||
avatar.setAttribute('peer', '' + command.userId);
|
|
||||||
|
|
||||||
const name = document.createElement('div');
|
|
||||||
name.classList.add('commands-helper-command-name');
|
|
||||||
name.innerText = '/' + command.command.command;
|
|
||||||
|
|
||||||
const description = document.createElement('div');
|
|
||||||
description.classList.add('commands-helper-command-description');
|
|
||||||
description.innerHTML = RichTextProcessor.wrapEmojiText(command.command.description);
|
|
||||||
|
|
||||||
div.append(avatar, name, description);
|
|
||||||
this.list.append(div);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.toggle(!commands.length);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
import type ChatInput from "./input";
|
import type ChatInput from "./input";
|
||||||
import { appendEmoji, getEmojiFromElement } from "../emoticonsDropdown/tabs/emoji";
|
import { appendEmoji, getEmojiFromElement } from "../emoticonsDropdown/tabs/emoji";
|
||||||
import { ScrollableX } from "../scrollable";
|
import { ScrollableX } from "../scrollable";
|
||||||
@ -54,5 +60,9 @@ export default class EmojiHelper extends AutocompleteHelper {
|
|||||||
|
|
||||||
this.waitForKey = waitForKey ? 'ArrowUp' : undefined;
|
this.waitForKey = waitForKey ? 'ArrowUp' : undefined;
|
||||||
this.toggle(!emojis.length);
|
this.toggle(!emojis.length);
|
||||||
|
|
||||||
|
/* window.requestAnimationFrame(() => {
|
||||||
|
this.container.style.width = (3 * 2) + (emojis.length * 44) + 'px';
|
||||||
|
}); */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -65,6 +65,7 @@ import CommandsHelper from './commandsHelper';
|
|||||||
import AutocompleteHelperController from './autocompleteHelperController';
|
import AutocompleteHelperController from './autocompleteHelperController';
|
||||||
import AutocompleteHelper from './autocompleteHelper';
|
import AutocompleteHelper from './autocompleteHelper';
|
||||||
import appUsersManager from '../../lib/appManagers/appUsersManager';
|
import appUsersManager from '../../lib/appManagers/appUsersManager';
|
||||||
|
import MentionsHelper from './mentionsHelper';
|
||||||
|
|
||||||
const RECORD_MIN_TIME = 500;
|
const RECORD_MIN_TIME = 500;
|
||||||
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
|
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
|
||||||
@ -136,9 +137,10 @@ export default class ChatInput {
|
|||||||
private canUndoFromHTML = '';
|
private canUndoFromHTML = '';
|
||||||
|
|
||||||
private autocompleteHelperController: AutocompleteHelperController;
|
private autocompleteHelperController: AutocompleteHelperController;
|
||||||
private commandsHelper: CommandsHelper;
|
|
||||||
private emojiHelper: EmojiHelper;
|
|
||||||
private stickersHelper: StickersHelper;
|
private stickersHelper: StickersHelper;
|
||||||
|
private emojiHelper: EmojiHelper;
|
||||||
|
private commandsHelper: CommandsHelper;
|
||||||
|
private mentionsHelper: MentionsHelper;
|
||||||
private listenerSetter: ListenerSetter;
|
private listenerSetter: ListenerSetter;
|
||||||
|
|
||||||
private pinnedControlBtn: HTMLButtonElement;
|
private pinnedControlBtn: HTMLButtonElement;
|
||||||
@ -374,6 +376,7 @@ export default class ChatInput {
|
|||||||
this.stickersHelper = new StickersHelper(this.rowsWrapper, this.autocompleteHelperController);
|
this.stickersHelper = new StickersHelper(this.rowsWrapper, this.autocompleteHelperController);
|
||||||
this.emojiHelper = new EmojiHelper(this.rowsWrapper, this.autocompleteHelperController, this);
|
this.emojiHelper = new EmojiHelper(this.rowsWrapper, this.autocompleteHelperController, this);
|
||||||
this.commandsHelper = new CommandsHelper(this.rowsWrapper, this.autocompleteHelperController, this);
|
this.commandsHelper = new CommandsHelper(this.rowsWrapper, this.autocompleteHelperController, this);
|
||||||
|
this.mentionsHelper = new MentionsHelper(this.rowsWrapper, this.autocompleteHelperController, this);
|
||||||
this.rowsWrapper.append(this.newMessageWrapper);
|
this.rowsWrapper.append(this.newMessageWrapper);
|
||||||
|
|
||||||
this.btnCancelRecord = ButtonIcon('delete danger btn-circle z-depth-1 btn-record-cancel');
|
this.btnCancelRecord = ButtonIcon('delete danger btn-circle z-depth-1 btn-record-cancel');
|
||||||
@ -1179,34 +1182,19 @@ export default class ChatInput {
|
|||||||
RichTextProcessor.mergeEntities(entities, addEntities);
|
RichTextProcessor.mergeEntities(entities, addEntities);
|
||||||
|
|
||||||
//const saveExecuted = this.prepareDocumentExecute();
|
//const saveExecuted = this.prepareDocumentExecute();
|
||||||
this.messageInputField.value = RichTextProcessor.wrapDraftText(newValue, {entities});
|
// can't exec .value here because it will instantly check for autocomplete
|
||||||
|
this.messageInputField.setValueSilently(RichTextProcessor.wrapDraftText(newValue, {entities}), true);
|
||||||
|
|
||||||
const caret = this.messageInput.querySelector('.composer-sel');
|
const caret = this.messageInput.querySelector('.composer-sel');
|
||||||
setRichFocus(this.messageInput, caret);
|
setRichFocus(this.messageInput, caret);
|
||||||
caret.remove();
|
caret.remove();
|
||||||
|
|
||||||
|
// but it's needed to be checked only here
|
||||||
|
this.onMessageInput();
|
||||||
|
|
||||||
//saveExecuted();
|
//saveExecuted();
|
||||||
|
|
||||||
//document.execCommand('insertHTML', true, RichTextProcessor.wrapEmojiText(emoji));
|
//document.execCommand('insertHTML', true, RichTextProcessor.wrapEmojiText(emoji));
|
||||||
|
|
||||||
//const str =
|
|
||||||
|
|
||||||
/* var newValuePrefix
|
|
||||||
if(matches && matches[0]) {
|
|
||||||
newValuePrefix = prefix.substr(0, matches.index) + ':' + emoji[1] + ':'
|
|
||||||
} else {
|
|
||||||
newValuePrefix = prefix + ':' + emoji[1] + ':'
|
|
||||||
}
|
|
||||||
|
|
||||||
if(suffix.length) {
|
|
||||||
const html = this.getRichHtml(newValuePrefix) + ' <span id="composer_sel' + ++selId + '"></span>' + this.getRichHtml(suffix)
|
|
||||||
this.richTextareaEl.html(html)
|
|
||||||
setRichFocus(textarea, $('#composer_sel' + this.selId)[0])
|
|
||||||
} else {
|
|
||||||
const html = this.getRichHtml(newValuePrefix) + ' '
|
|
||||||
this.richTextareaEl.html(html)
|
|
||||||
setRichFocus(textarea)
|
|
||||||
} */
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1247,6 +1235,9 @@ export default class ChatInput {
|
|||||||
let foundHelper: AutocompleteHelper;
|
let foundHelper: AutocompleteHelper;
|
||||||
const entity = entities[0];
|
const entity = entities[0];
|
||||||
|
|
||||||
|
const query = matches[2];
|
||||||
|
const firstChar = query[0];
|
||||||
|
|
||||||
if(this.stickersHelper &&
|
if(this.stickersHelper &&
|
||||||
rootScope.settings.stickers.suggest &&
|
rootScope.settings.stickers.suggest &&
|
||||||
(this.chat.peerId > 0 || this.appChatsManager.hasRights(this.chat.peerId, 'send_stickers')) &&
|
(this.chat.peerId > 0 || this.appChatsManager.hasRights(this.chat.peerId, 'send_stickers')) &&
|
||||||
@ -1254,50 +1245,45 @@ export default class ChatInput {
|
|||||||
foundHelper = this.stickersHelper;
|
foundHelper = this.stickersHelper;
|
||||||
this.stickersHelper.checkEmoticon(value);
|
this.stickersHelper.checkEmoticon(value);
|
||||||
} else
|
} else
|
||||||
//let query = cleanSearchText(matches[2]);
|
//let query = cleanSearchText(query);
|
||||||
//const firstChar = matches[2][0];
|
|
||||||
|
|
||||||
//console.log('autocomplete matches', matches);
|
//console.log('autocomplete matches', matches);
|
||||||
|
|
||||||
/*if (matches[2] == '@') { // mentions
|
/* if(firstChar === '@') { // mentions
|
||||||
if (this.mentions && this.mentions.index) {
|
if(this.chat.peerId < 0) {
|
||||||
if (query.length) {
|
foundHelper = this.mentionsHelper;
|
||||||
var foundObject = SearchIndexManager.search(query, this.mentions.index)
|
this.chat.appProfileManager.getMentions(-this.chat.peerId, query).then(peerIds => {
|
||||||
var foundUsers = []
|
this.mentionsHelper.render(peerIds.map(peerId => {
|
||||||
var user
|
const user = this.chat.appUsersManager.getUser(peerId);
|
||||||
for (var i = 0, length = this.mentions.users.length; i < length; i++) {
|
return {
|
||||||
user = this.mentions.users[i]
|
peerId,
|
||||||
if (foundObject[user.id]) {
|
description: user.username ? '@' + user.username : undefined
|
||||||
foundUsers.push(user)
|
};
|
||||||
}
|
}));
|
||||||
}
|
});
|
||||||
} else {
|
|
||||||
var foundUsers = this.mentions.users
|
|
||||||
}
|
|
||||||
if (foundUsers.length) {
|
|
||||||
this.showMentionSuggestions(foundUsers)
|
|
||||||
} else {
|
|
||||||
this.hideSuggestions()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.hideSuggestions()
|
|
||||||
}
|
}
|
||||||
} else */ if(!matches[1] && matches[2][0] === '/') { // commands
|
} else */if(!matches[1] && firstChar === '/') { // commands
|
||||||
if(appUsersManager.isBot(this.chat.peerId)) {
|
if(appUsersManager.isBot(this.chat.peerId)) {
|
||||||
foundHelper = this.commandsHelper;
|
foundHelper = this.commandsHelper;
|
||||||
this.chat.appProfileManager.getProfileByPeerId(this.chat.peerId).then(full => {
|
this.chat.appProfileManager.getProfileByPeerId(this.chat.peerId).then(full => {
|
||||||
const botInfos: BotInfo.botInfo[] = [].concat(full.bot_info);
|
const botInfos: BotInfo.botInfo[] = [].concat(full.bot_info);
|
||||||
const index = new SearchIndex<string>(false, false);
|
const index = new SearchIndex<string>(true, false);
|
||||||
|
|
||||||
const commands: Map<string, {userId: number, command: BotCommand}> = new Map();
|
const commands: Map<string, {peerId: number, name: string, description: string}> = new Map();
|
||||||
botInfos.forEach(botInfo => {
|
botInfos.forEach(botInfo => {
|
||||||
botInfo.commands.forEach(botCommand => {
|
botInfo.commands.forEach(botCommand => {
|
||||||
commands.set(botCommand.command, {userId: botInfo.user_id, command: botCommand});
|
const c = '/' + botCommand.command;
|
||||||
index.indexObject(botCommand.command, '/' + botCommand.command);
|
commands.set(botCommand.command, {
|
||||||
|
peerId: botInfo.user_id,
|
||||||
|
name: c,
|
||||||
|
description: botCommand.description
|
||||||
|
});
|
||||||
|
|
||||||
|
index.indexObject(botCommand.command, c);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const found = index.search(matches[2]);
|
const found = index.search(query);
|
||||||
const filtered = Array.from(found).map(command => commands.get(command));
|
const filtered = Array.from(found).map(command => commands.get(command));
|
||||||
this.commandsHelper.render(filtered);
|
this.commandsHelper.render(filtered);
|
||||||
// console.log('found commands', found, filtered);
|
// console.log('found commands', found, filtered);
|
||||||
@ -1307,9 +1293,9 @@ export default class ChatInput {
|
|||||||
if(!value.match(/^\s*:(.+):\s*$/)) {
|
if(!value.match(/^\s*:(.+):\s*$/)) {
|
||||||
foundHelper = this.emojiHelper;
|
foundHelper = this.emojiHelper;
|
||||||
this.appEmojiManager.getBothEmojiKeywords().then(() => {
|
this.appEmojiManager.getBothEmojiKeywords().then(() => {
|
||||||
const q = matches[2].replace(/^:/, '');
|
const q = query.replace(/^:/, '');
|
||||||
const emojis = this.appEmojiManager.searchEmojis(q);
|
const emojis = this.appEmojiManager.searchEmojis(q);
|
||||||
this.emojiHelper.render(emojis, matches[2][0] !== ':');
|
this.emojiHelper.render(emojis, firstChar !== ':');
|
||||||
//console.log(emojis);
|
//console.log(emojis);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
24
src/components/chat/mentionsHelper.ts
Normal file
24
src/components/chat/mentionsHelper.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
/*
|
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
import type ChatInput from "./input";
|
||||||
|
import AutocompleteHelperController from "./autocompleteHelperController";
|
||||||
|
import AutocompletePeerHelper from "./autocompletePeerHelper";
|
||||||
|
import placeCaretAtEnd from "../../helpers/dom/placeCaretAtEnd";
|
||||||
|
|
||||||
|
export default class MentionsHelper extends AutocompletePeerHelper {
|
||||||
|
constructor(appendTo: HTMLElement, controller: AutocompleteHelperController, private chatInput: ChatInput) {
|
||||||
|
super(appendTo,
|
||||||
|
controller,
|
||||||
|
'mentions-helper',
|
||||||
|
(target) => {
|
||||||
|
const innerHTML = target.querySelector(`.${AutocompletePeerHelper.BASE_CLASS_LIST_ELEMENT}-description`).innerHTML;
|
||||||
|
chatInput.messageInputField.value = innerHTML + ' ';
|
||||||
|
placeCaretAtEnd(chatInput.messageInput);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,8 @@
|
|||||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import mediaSizes from "../../helpers/mediaSizes";
|
||||||
|
import { clamp } from "../../helpers/number";
|
||||||
import { MyDocument } from "../../lib/appManagers/appDocsManager";
|
import { MyDocument } from "../../lib/appManagers/appDocsManager";
|
||||||
import { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager";
|
import { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager";
|
||||||
import appStickersManager from "../../lib/appManagers/appStickersManager";
|
import appStickersManager from "../../lib/appManagers/appStickersManager";
|
||||||
@ -18,6 +20,7 @@ export default class StickersHelper extends AutocompleteHelper {
|
|||||||
private scrollable: Scrollable;
|
private scrollable: Scrollable;
|
||||||
private superStickerRenderer: SuperStickerRenderer;
|
private superStickerRenderer: SuperStickerRenderer;
|
||||||
private lazyLoadQueue: LazyLoadQueue;
|
private lazyLoadQueue: LazyLoadQueue;
|
||||||
|
private onChangeScreen: () => void;
|
||||||
|
|
||||||
constructor(appendTo: HTMLElement, controller: AutocompleteHelperController) {
|
constructor(appendTo: HTMLElement, controller: AutocompleteHelperController) {
|
||||||
super({
|
super({
|
||||||
@ -37,6 +40,13 @@ export default class StickersHelper extends AutocompleteHelper {
|
|||||||
this.scrollable.container.scrollTop = 0;
|
this.scrollable.container.scrollTop = 0;
|
||||||
}, 0);
|
}, 0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
this.addEventListener('hidden', () => {
|
||||||
|
if(this.onChangeScreen) {
|
||||||
|
mediaSizes.removeEventListener('changeScreen', this.onChangeScreen);
|
||||||
|
this.onChangeScreen = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public checkEmoticon(emoticon: string) {
|
public checkEmoticon(emoticon: string) {
|
||||||
@ -79,6 +89,15 @@ export default class StickersHelper extends AutocompleteHelper {
|
|||||||
this.list.replaceWith(container);
|
this.list.replaceWith(container);
|
||||||
this.list = container;
|
this.list = container;
|
||||||
|
|
||||||
|
if(!this.onChangeScreen) {
|
||||||
|
this.onChangeScreen = () => {
|
||||||
|
this.list.style.width = this.list.childElementCount * mediaSizes.active.esgSticker.width + 'px';
|
||||||
|
};
|
||||||
|
mediaSizes.addEventListener('changeScreen', this.onChangeScreen);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.onChangeScreen();
|
||||||
|
|
||||||
this.toggle(!stickers.length);
|
this.toggle(!stickers.length);
|
||||||
this.scrollable.scrollTop = 0;
|
this.scrollable.scrollTop = 0;
|
||||||
});
|
});
|
||||||
|
@ -29,7 +29,7 @@ export function appendEmoji(emoji: string, container: HTMLElement, prepend = fal
|
|||||||
spanEmoji.classList.add('super-emoji');
|
spanEmoji.classList.add('super-emoji');
|
||||||
|
|
||||||
let kek: string;
|
let kek: string;
|
||||||
if(unify) {
|
if(unify && !RichTextProcessor.emojiSupported) {
|
||||||
kek = RichTextProcessor.wrapSingleEmoji(emoji);
|
kek = RichTextProcessor.wrapSingleEmoji(emoji);
|
||||||
} else {
|
} else {
|
||||||
emoji = RichTextProcessor.fixEmoji(emoji);
|
emoji = RichTextProcessor.fixEmoji(emoji);
|
||||||
|
@ -19,6 +19,7 @@ import { ChannelParticipantsFilter, ChannelsChannelParticipants, Chat, ChatFull,
|
|||||||
import apiManager from '../mtproto/mtprotoworker';
|
import apiManager from '../mtproto/mtprotoworker';
|
||||||
import { RichTextProcessor } from "../richtextprocessor";
|
import { RichTextProcessor } from "../richtextprocessor";
|
||||||
import rootScope from "../rootScope";
|
import rootScope from "../rootScope";
|
||||||
|
import SearchIndex from "../searchIndex";
|
||||||
import apiUpdatesManager from "./apiUpdatesManager";
|
import apiUpdatesManager from "./apiUpdatesManager";
|
||||||
import appChatsManager from "./appChatsManager";
|
import appChatsManager from "./appChatsManager";
|
||||||
import appDownloadManager from "./appDownloadManager";
|
import appDownloadManager from "./appDownloadManager";
|
||||||
@ -387,6 +388,17 @@ export class AppProfileManager {
|
|||||||
}) as any;
|
}) as any;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getMentions(chatId: number, query: string): Promise<number[]> {
|
||||||
|
return (this.getChatFull(chatId) as Promise<ChatFull.chatFull>).then(chatFull => {
|
||||||
|
const index = new SearchIndex<number>(true, true);
|
||||||
|
(chatFull.participants as ChatParticipants.chatParticipants).participants.forEach(participant => {
|
||||||
|
index.indexObject(participant.user_id, appUsersManager.getUserSearchText(participant.user_id));
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(index.search(query));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public invalidateChannelParticipants(id: number) {
|
public invalidateChannelParticipants(id: number) {
|
||||||
delete this.chatsFull[id];
|
delete this.chatsFull[id];
|
||||||
delete this.fullPromises[-id];
|
delete this.fullPromises[-id];
|
||||||
|
@ -413,7 +413,7 @@ namespace RichTextProcessor {
|
|||||||
fromBot: boolean,
|
fromBot: boolean,
|
||||||
noTextFormat: true,
|
noTextFormat: true,
|
||||||
passEntities: Partial<{
|
passEntities: Partial<{
|
||||||
[_ in MessageEntity['_']]: true
|
[_ in MessageEntity['_']]: boolean
|
||||||
}>,
|
}>,
|
||||||
|
|
||||||
contextHashtag?: string
|
contextHashtag?: string
|
||||||
@ -513,7 +513,8 @@ namespace RichTextProcessor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case 'messageEntityBotCommand': {
|
case 'messageEntityBotCommand': {
|
||||||
if(!(options.noLinks || options.noCommands || contextExternal) && !entity.unsafe) {
|
// if(!(options.noLinks || options.noCommands || contextExternal)/* && !entity.unsafe */) {
|
||||||
|
if(!options.noLinks && passEntities[entity._]) {
|
||||||
const entityText = text.substr(entity.offset, entity.length);
|
const entityText = text.substr(entity.offset, entity.length);
|
||||||
let command = entityText.substr(1);
|
let command = entityText.substr(1);
|
||||||
let bot: string | boolean;
|
let bot: string | boolean;
|
||||||
@ -525,7 +526,7 @@ namespace RichTextProcessor {
|
|||||||
bot = options.fromBot;
|
bot = options.fromBot;
|
||||||
}
|
}
|
||||||
|
|
||||||
insertPart(entity, `<a href="${encodeEntities('tg://bot_command?command=' + encodeURIComponent(command) + (bot ? '&bot=' + encodeURIComponent(bot) : ''))}" onclick="execBotCommand(this)">`, `</a>`);
|
insertPart(entity, `<a href="${encodeEntities('tg://bot_command?command=' + encodeURIComponent(command) + (bot ? '&bot=' + encodeURIComponent(bot) : ''))}" ${contextExternal ? '' : 'onclick="execBotCommand(this)"'}>`, `</a>`);
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -24,7 +24,7 @@ export default class SearchIndex<SearchWhat> {
|
|||||||
} */
|
} */
|
||||||
|
|
||||||
if(searchText.trim() && this.cleanText) {
|
if(searchText.trim() && this.cleanText) {
|
||||||
searchText = cleanSearchText(searchText);
|
searchText = cleanSearchText(searchText, this.latinize);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!searchText) {
|
if(!searchText) {
|
||||||
@ -59,9 +59,11 @@ export default class SearchIndex<SearchWhat> {
|
|||||||
|
|
||||||
const newFoundObjs: Array<{fullText: string, what: SearchWhat}> = [];
|
const newFoundObjs: Array<{fullText: string, what: SearchWhat}> = [];
|
||||||
const queryWords = query.split(' ');
|
const queryWords = query.split(' ');
|
||||||
|
const queryWordsLength = queryWords.length;
|
||||||
fullTexts.forEach((fullText, what) => {
|
fullTexts.forEach((fullText, what) => {
|
||||||
let found = true;
|
let found = true;
|
||||||
for(const word of queryWords) { // * verify that all words are found
|
for(let i = 0; i < queryWordsLength; ++i) { // * verify that all words are found
|
||||||
|
const word = queryWords[i];
|
||||||
const idx = fullText.indexOf(word);
|
const idx = fullText.indexOf(word);
|
||||||
if(idx === -1 || (idx !== 0 && fullText[idx - 1] !== ' ')) { // * search only from word beginning
|
if(idx === -1 || (idx !== 0 && fullText[idx - 1] !== ' ')) { // * search only from word beginning
|
||||||
found = false;
|
found = false;
|
||||||
|
@ -11,6 +11,9 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
padding: 0 !important;
|
padding: 0 !important;
|
||||||
border-radius: var(--border-radius) !important;
|
border-radius: var(--border-radius) !important;
|
||||||
|
max-width: 100%;
|
||||||
|
left: 0;
|
||||||
|
width: auto !important;
|
||||||
|
|
||||||
&:not(.is-visible) {
|
&:not(.is-visible) {
|
||||||
display: none;
|
display: none;
|
||||||
@ -21,7 +24,7 @@
|
|||||||
animation: fade-out-opacity .2s ease-in-out forwards;
|
animation: fade-out-opacity .2s ease-in-out forwards;
|
||||||
|
|
||||||
&:not(.backwards) {
|
&:not(.backwards) {
|
||||||
animation: fade-in-opacity .2s ease-in-out forwards;
|
animation-name: fade-in-opacity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
44
src/scss/partials/_autocompletePeerHelper.scss
Normal file
44
src/scss/partials/_autocompletePeerHelper.scss
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
.autocomplete-peer-helper {
|
||||||
|
.scrollable {
|
||||||
|
position: relative;
|
||||||
|
max-height: 232px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-list {
|
||||||
|
padding: .5rem 0;
|
||||||
|
|
||||||
|
&-element {
|
||||||
|
height: 3.125rem;
|
||||||
|
display: flex;
|
||||||
|
// padding: 0 .75rem;
|
||||||
|
padding: 0 2.125rem 0px 0.75rem;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
position: relative;
|
||||||
|
line-height: var(--line-height);
|
||||||
|
|
||||||
|
@include hover();
|
||||||
|
|
||||||
|
&-name {
|
||||||
|
margin-left: .875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-description {
|
||||||
|
margin-left: .5625rem;
|
||||||
|
color: var(--secondary-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* .mentions-helper {
|
||||||
|
width: 320px !important;
|
||||||
|
} */
|
@ -1,33 +0,0 @@
|
|||||||
.commands-helper {
|
|
||||||
left: 0;
|
|
||||||
//width: 320px !important;
|
|
||||||
|
|
||||||
.scrollable {
|
|
||||||
position: relative;
|
|
||||||
max-height: 232px;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-commands {
|
|
||||||
padding: .5rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-command {
|
|
||||||
height: 3.125rem;
|
|
||||||
display: flex;
|
|
||||||
padding: 0 .75rem;
|
|
||||||
align-items: center;
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
@include hover();
|
|
||||||
|
|
||||||
&-name {
|
|
||||||
margin-left: .875rem;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-description {
|
|
||||||
margin-left: .5625rem;
|
|
||||||
color: var(--secondary-text-color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,9 +1,11 @@
|
|||||||
.emoji-helper {
|
.emoji-helper {
|
||||||
height: 50px;
|
height: 50px;
|
||||||
padding: .25rem 0 !important;
|
padding: .25rem 0 !important;
|
||||||
|
//transition: width .15s ease-in-out;
|
||||||
|
|
||||||
> .scrollable {
|
> .scrollable {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.super-emojis {
|
.super-emojis {
|
||||||
|
@ -13,8 +13,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
&-stickers {
|
&-stickers {
|
||||||
display: flex;
|
max-width: 100%;
|
||||||
flex-wrap: wrap;
|
/* display: flex !important;
|
||||||
|
flex-wrap: wrap; */
|
||||||
border-radius: var(--border-radius);
|
border-radius: var(--border-radius);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,4 +24,10 @@
|
|||||||
background: none;
|
background: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* .super-sticker {
|
||||||
|
width: var(--esg-sticker-size);
|
||||||
|
height: var(--esg-sticker-size);
|
||||||
|
padding: 0;
|
||||||
|
} */
|
||||||
}
|
}
|
||||||
|
@ -226,6 +226,7 @@ html.night {
|
|||||||
@import "partials/button";
|
@import "partials/button";
|
||||||
@import "partials/animatedIcon";
|
@import "partials/animatedIcon";
|
||||||
@import "partials/autocompleteHelper";
|
@import "partials/autocompleteHelper";
|
||||||
|
@import "partials/autocompletePeerHelper";
|
||||||
@import "partials/badge";
|
@import "partials/badge";
|
||||||
@import "partials/checkbox";
|
@import "partials/checkbox";
|
||||||
@import "partials/chatlist";
|
@import "partials/chatlist";
|
||||||
@ -236,7 +237,6 @@ html.night {
|
|||||||
@import "partials/chatMarkupTooltip";
|
@import "partials/chatMarkupTooltip";
|
||||||
@import "partials/chatStickersHelper";
|
@import "partials/chatStickersHelper";
|
||||||
@import "partials/chatEmojiHelper";
|
@import "partials/chatEmojiHelper";
|
||||||
@import "partials/chatCommandsHelper";
|
|
||||||
@import "partials/chatSearch";
|
@import "partials/chatSearch";
|
||||||
@import "partials/chatDrop";
|
@import "partials/chatDrop";
|
||||||
@import "partials/crop";
|
@import "partials/crop";
|
||||||
|
Loading…
x
Reference in New Issue
Block a user