Chat input autocomplete helper
Fix iOS 13 blank page
This commit is contained in:
parent
f156557b47
commit
5505ef5b8b
@ -17,7 +17,6 @@ import appUsersManager from "../lib/appManagers/appUsersManager";
|
|||||||
import { logger } from "../lib/logger";
|
import { logger } from "../lib/logger";
|
||||||
import RichTextProcessor from "../lib/richtextprocessor";
|
import RichTextProcessor from "../lib/richtextprocessor";
|
||||||
import rootScope from "../lib/rootScope";
|
import rootScope from "../lib/rootScope";
|
||||||
import searchIndexManager from "../lib/searchIndexManager";
|
|
||||||
import AppMediaViewer from "./appMediaViewer";
|
import AppMediaViewer from "./appMediaViewer";
|
||||||
import { SearchGroup, SearchGroupType } from "./appSearch";
|
import { SearchGroup, SearchGroupType } from "./appSearch";
|
||||||
import { horizontalMenu } from "./horizontalMenu";
|
import { horizontalMenu } from "./horizontalMenu";
|
||||||
@ -39,6 +38,7 @@ import appSidebarRight from "./sidebarRight";
|
|||||||
import mediaSizes from "../helpers/mediaSizes";
|
import mediaSizes from "../helpers/mediaSizes";
|
||||||
import appImManager from "../lib/appManagers/appImManager";
|
import appImManager from "../lib/appManagers/appImManager";
|
||||||
import positionElementByIndex from "../helpers/dom/positionElementByIndex";
|
import positionElementByIndex from "../helpers/dom/positionElementByIndex";
|
||||||
|
import cleanSearchText from "../helpers/cleanSearchText";
|
||||||
|
|
||||||
//const testScroll = false;
|
//const testScroll = false;
|
||||||
|
|
||||||
@ -738,7 +738,7 @@ export default class AppSearchSuper {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if(showMembersCount && (peer.participants_count || peer.participants)) {
|
if(showMembersCount && (peer.participants_count || peer.participants)) {
|
||||||
const regExp = new RegExp(`(${escapeRegExp(query)}|${escapeRegExp(searchIndexManager.cleanSearchText(query))})`, 'gi');
|
const regExp = new RegExp(`(${escapeRegExp(query)}|${escapeRegExp(cleanSearchText(query))})`, 'gi');
|
||||||
dom.titleSpan.innerHTML = dom.titleSpan.innerHTML.replace(regExp, '<i>$1</i>');
|
dom.titleSpan.innerHTML = dom.titleSpan.innerHTML.replace(regExp, '<i>$1</i>');
|
||||||
dom.lastMessageSpan.append(appChatsManager.getChatMembersString(-peerId));
|
dom.lastMessageSpan.append(appChatsManager.getChatMembersString(-peerId));
|
||||||
} else if(peerId === rootScope.myId) {
|
} else if(peerId === rootScope.myId) {
|
||||||
|
@ -19,6 +19,7 @@ import type { AppUsersManager } from "../../lib/appManagers/appUsersManager";
|
|||||||
import type { AppWebPagesManager } from "../../lib/appManagers/appWebPagesManager";
|
import type { AppWebPagesManager } from "../../lib/appManagers/appWebPagesManager";
|
||||||
import type { ApiManagerProxy } from "../../lib/mtproto/mtprotoworker";
|
import type { ApiManagerProxy } from "../../lib/mtproto/mtprotoworker";
|
||||||
import type { AppDraftsManager } from "../../lib/appManagers/appDraftsManager";
|
import type { AppDraftsManager } from "../../lib/appManagers/appDraftsManager";
|
||||||
|
import type { AppEmojiManager } from "../../lib/appManagers/appEmojiManager";
|
||||||
import type { ServerTimeManager } from "../../lib/mtproto/serverTimeManager";
|
import type { ServerTimeManager } from "../../lib/mtproto/serverTimeManager";
|
||||||
import type sessionStorage from '../../lib/sessionStorage';
|
import type sessionStorage from '../../lib/sessionStorage';
|
||||||
import EventListenerBase from "../../helpers/eventListenerBase";
|
import EventListenerBase from "../../helpers/eventListenerBase";
|
||||||
@ -64,7 +65,25 @@ export default class Chat extends EventListenerBase<{
|
|||||||
|
|
||||||
public noAutoDownloadMedia: boolean;
|
public noAutoDownloadMedia: boolean;
|
||||||
|
|
||||||
constructor(public appImManager: AppImManager, public appChatsManager: AppChatsManager, public appDocsManager: AppDocsManager, public appInlineBotsManager: AppInlineBotsManager, public appMessagesManager: AppMessagesManager, public appPeersManager: AppPeersManager, public appPhotosManager: AppPhotosManager, public appProfileManager: AppProfileManager, public appStickersManager: AppStickersManager, public appUsersManager: AppUsersManager, public appWebPagesManager: AppWebPagesManager, public appPollsManager: AppPollsManager, public apiManager: ApiManagerProxy, public appDraftsManager: AppDraftsManager, public serverTimeManager: ServerTimeManager, public storage: typeof sessionStorage, public appNotificationsManager: AppNotificationsManager) {
|
constructor(public appImManager: AppImManager,
|
||||||
|
public appChatsManager: AppChatsManager,
|
||||||
|
public appDocsManager: AppDocsManager,
|
||||||
|
public appInlineBotsManager: AppInlineBotsManager,
|
||||||
|
public appMessagesManager: AppMessagesManager,
|
||||||
|
public appPeersManager: AppPeersManager,
|
||||||
|
public appPhotosManager: AppPhotosManager,
|
||||||
|
public appProfileManager: AppProfileManager,
|
||||||
|
public appStickersManager: AppStickersManager,
|
||||||
|
public appUsersManager: AppUsersManager,
|
||||||
|
public appWebPagesManager: AppWebPagesManager,
|
||||||
|
public appPollsManager: AppPollsManager,
|
||||||
|
public apiManager: ApiManagerProxy,
|
||||||
|
public appDraftsManager: AppDraftsManager,
|
||||||
|
public serverTimeManager: ServerTimeManager,
|
||||||
|
public storage: typeof sessionStorage,
|
||||||
|
public appNotificationsManager: AppNotificationsManager,
|
||||||
|
public appEmojiManager: AppEmojiManager
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
this.container = document.createElement('div');
|
this.container = document.createElement('div');
|
||||||
@ -150,7 +169,7 @@ export default class Chat extends EventListenerBase<{
|
|||||||
|
|
||||||
this.topbar = new ChatTopbar(this, appSidebarRight, this.appMessagesManager, this.appPeersManager, this.appChatsManager, this.appNotificationsManager);
|
this.topbar = new ChatTopbar(this, appSidebarRight, this.appMessagesManager, this.appPeersManager, this.appChatsManager, this.appNotificationsManager);
|
||||||
this.bubbles = new ChatBubbles(this, this.appMessagesManager, this.appStickersManager, this.appUsersManager, this.appInlineBotsManager, this.appPhotosManager, this.appDocsManager, this.appPeersManager, this.appChatsManager, this.storage);
|
this.bubbles = new ChatBubbles(this, this.appMessagesManager, this.appStickersManager, this.appUsersManager, this.appInlineBotsManager, this.appPhotosManager, this.appDocsManager, this.appPeersManager, this.appChatsManager, this.storage);
|
||||||
this.input = new ChatInput(this, this.appMessagesManager, this.appDocsManager, this.appChatsManager, this.appPeersManager, this.appWebPagesManager, this.appImManager, this.appDraftsManager, this.serverTimeManager, this.appNotificationsManager);
|
this.input = new ChatInput(this, this.appMessagesManager, this.appDocsManager, this.appChatsManager, this.appPeersManager, this.appWebPagesManager, this.appImManager, this.appDraftsManager, this.serverTimeManager, this.appNotificationsManager, this.appEmojiManager);
|
||||||
this.selection = new ChatSelection(this, this.bubbles, this.input, this.appMessagesManager);
|
this.selection = new ChatSelection(this, this.bubbles, this.input, this.appMessagesManager);
|
||||||
this.contextMenu = new ChatContextMenu(this.bubbles.bubblesContainer, this, this.appMessagesManager, this.appChatsManager, this.appPeersManager, this.appPollsManager);
|
this.contextMenu = new ChatContextMenu(this.bubbles.bubblesContainer, this, this.appMessagesManager, this.appChatsManager, this.appPeersManager, this.appPollsManager);
|
||||||
|
|
||||||
|
58
src/components/chat/emojiHelper.ts
Normal file
58
src/components/chat/emojiHelper.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import type ChatInput from "./input";
|
||||||
|
import attachListNavigation from "../../helpers/dom/attachlistNavigation";
|
||||||
|
import { appendEmoji, getEmojiFromElement } from "../emoticonsDropdown/tabs/emoji";
|
||||||
|
import { ScrollableX } from "../scrollable";
|
||||||
|
import AutocompleteHelper from "./autocompleteHelper";
|
||||||
|
|
||||||
|
export default class EmojiHelper extends AutocompleteHelper {
|
||||||
|
private emojisContainer: HTMLDivElement;
|
||||||
|
private scrollable: ScrollableX;
|
||||||
|
|
||||||
|
constructor(appendTo: HTMLElement, private chatInput: ChatInput) {
|
||||||
|
super(appendTo);
|
||||||
|
|
||||||
|
this.container.classList.add('emoji-helper');
|
||||||
|
|
||||||
|
this.addEventListener('visible', () => {
|
||||||
|
const list = this.emojisContainer;
|
||||||
|
const {detach} = attachListNavigation({
|
||||||
|
list,
|
||||||
|
type: 'x',
|
||||||
|
onSelect: (target) => {
|
||||||
|
this.chatInput.onEmojiSelected(getEmojiFromElement(target as any), true);
|
||||||
|
},
|
||||||
|
once: true
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addEventListener('hidden', () => {
|
||||||
|
list.innerHTML = '';
|
||||||
|
detach();
|
||||||
|
}, true);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private init() {
|
||||||
|
this.emojisContainer = document.createElement('div');
|
||||||
|
this.emojisContainer.classList.add('emoji-helper-emojis', 'super-emojis');
|
||||||
|
|
||||||
|
this.container.append(this.emojisContainer);
|
||||||
|
|
||||||
|
this.scrollable = new ScrollableX(this.container);
|
||||||
|
}
|
||||||
|
|
||||||
|
public renderEmojis(emojis: string[]) {
|
||||||
|
if(this.init) {
|
||||||
|
this.init();
|
||||||
|
this.init = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(emojis.length) {
|
||||||
|
this.emojisContainer.innerHTML = '';
|
||||||
|
emojis.forEach(emoji => {
|
||||||
|
appendEmoji(emoji, this.emojisContainer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.toggle(!emojis.length);
|
||||||
|
}
|
||||||
|
}
|
@ -12,6 +12,7 @@ import type { AppPeersManager } from '../../lib/appManagers/appPeersManager';
|
|||||||
import type { AppWebPagesManager } from "../../lib/appManagers/appWebPagesManager";
|
import type { AppWebPagesManager } from "../../lib/appManagers/appWebPagesManager";
|
||||||
import type { AppImManager } from '../../lib/appManagers/appImManager';
|
import type { AppImManager } from '../../lib/appManagers/appImManager';
|
||||||
import type { AppDraftsManager, MyDraftMessage } from '../../lib/appManagers/appDraftsManager';
|
import type { AppDraftsManager, MyDraftMessage } from '../../lib/appManagers/appDraftsManager';
|
||||||
|
import type { AppEmojiManager } from '../../lib/appManagers/appEmojiManager';
|
||||||
import type { ServerTimeManager } from '../../lib/mtproto/serverTimeManager';
|
import type { ServerTimeManager } from '../../lib/mtproto/serverTimeManager';
|
||||||
import type Chat from './chat';
|
import type Chat from './chat';
|
||||||
import Recorder from '../../../public/recorder.min';
|
import Recorder from '../../../public/recorder.min';
|
||||||
@ -57,50 +58,52 @@ import isSendShortcutPressed from '../../helpers/dom/isSendShortcutPressed';
|
|||||||
import placeCaretAtEnd from '../../helpers/dom/placeCaretAtEnd';
|
import placeCaretAtEnd from '../../helpers/dom/placeCaretAtEnd';
|
||||||
import { MarkdownType, markdownTags } from '../../helpers/dom/getRichElementValue';
|
import { MarkdownType, markdownTags } from '../../helpers/dom/getRichElementValue';
|
||||||
import getRichValueWithCaret from '../../helpers/dom/getRichValueWithCaret';
|
import getRichValueWithCaret from '../../helpers/dom/getRichValueWithCaret';
|
||||||
import searchIndexManager from '../../lib/searchIndexManager';
|
import cleanSearchText from '../../helpers/cleanSearchText';
|
||||||
|
import EmojiHelper from './emojiHelper';
|
||||||
|
|
||||||
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.';
|
||||||
|
|
||||||
type ChatInputHelperType = 'edit' | 'webpage' | 'forward' | 'reply';
|
type ChatInputHelperType = 'edit' | 'webpage' | 'forward' | 'reply';
|
||||||
|
|
||||||
|
let selId = 0;
|
||||||
|
|
||||||
export default class ChatInput {
|
export default class ChatInput {
|
||||||
public static AUTO_COMPLETE_REG_EXP = /(\s|^)(:|@|\/)([\S]*)$/;
|
public static AUTO_COMPLETE_REG_EXP = /(\s|^)((?::|.)(?!.*:).*|(?:(?:@|\/)(?:[\S]*)))$/;
|
||||||
public pageEl = document.getElementById('page-chats') as HTMLDivElement;
|
|
||||||
public messageInput: HTMLElement;
|
public messageInput: HTMLElement;
|
||||||
public messageInputField: InputField;
|
public messageInputField: InputField;
|
||||||
public fileInput: HTMLInputElement;
|
private fileInput: HTMLInputElement;
|
||||||
public inputMessageContainer: HTMLDivElement;
|
private inputMessageContainer: HTMLDivElement;
|
||||||
public btnSend = document.getElementById('btn-send') as HTMLButtonElement;
|
private btnSend: HTMLButtonElement;
|
||||||
public btnCancelRecord: HTMLButtonElement;
|
private btnCancelRecord: HTMLButtonElement;
|
||||||
public lastUrl = '';
|
private lastUrl = '';
|
||||||
public lastTimeType = 0;
|
private lastTimeType = 0;
|
||||||
|
|
||||||
public chatInput: HTMLElement;
|
public chatInput: HTMLElement;
|
||||||
public inputContainer: HTMLElement;
|
private inputContainer: HTMLElement;
|
||||||
public rowsWrapper: HTMLDivElement;
|
public rowsWrapper: HTMLDivElement;
|
||||||
private newMessageWrapper: HTMLDivElement;
|
private newMessageWrapper: HTMLDivElement;
|
||||||
private btnToggleEmoticons: HTMLButtonElement;
|
private btnToggleEmoticons: HTMLButtonElement;
|
||||||
public btnSendContainer: HTMLDivElement;
|
private btnSendContainer: HTMLDivElement;
|
||||||
|
|
||||||
public attachMenu: HTMLButtonElement;
|
private attachMenu: HTMLButtonElement;
|
||||||
private attachMenuButtons: (ButtonMenuItemOptions & {verify: (peerId: number) => boolean})[];
|
private attachMenuButtons: (ButtonMenuItemOptions & {verify: (peerId: number) => boolean})[];
|
||||||
|
|
||||||
public sendMenu: SendMenu;
|
private sendMenu: SendMenu;
|
||||||
|
|
||||||
public replyElements: {
|
private replyElements: {
|
||||||
container?: HTMLElement,
|
container?: HTMLElement,
|
||||||
cancelBtn?: HTMLButtonElement,
|
cancelBtn?: HTMLButtonElement,
|
||||||
titleEl?: HTMLElement,
|
titleEl?: HTMLElement,
|
||||||
subtitleEl?: HTMLElement
|
subtitleEl?: HTMLElement
|
||||||
} = {};
|
} = {};
|
||||||
|
|
||||||
public willSendWebPage: any = null;
|
private willSendWebPage: any = null;
|
||||||
public forwardingMids: number[] = [];
|
private forwardingMids: number[] = [];
|
||||||
public forwardingFromPeerId: number = 0;
|
private forwardingFromPeerId: number = 0;
|
||||||
public replyToMsgId: number;
|
public replyToMsgId: number;
|
||||||
public editMsgId: number;
|
public editMsgId: number;
|
||||||
public noWebPage: true;
|
private noWebPage: true;
|
||||||
public scheduleDate: number;
|
public scheduleDate: number;
|
||||||
public sendSilent: true;
|
public sendSilent: true;
|
||||||
|
|
||||||
@ -127,23 +130,35 @@ export default class ChatInput {
|
|||||||
readonly executedHistory: string[] = [];
|
readonly executedHistory: string[] = [];
|
||||||
private canUndoFromHTML = '';
|
private canUndoFromHTML = '';
|
||||||
|
|
||||||
public stickersHelper: StickersHelper;
|
private emojiHelper: EmojiHelper;
|
||||||
public listenerSetter: ListenerSetter;
|
private stickersHelper: StickersHelper;
|
||||||
|
private listenerSetter: ListenerSetter;
|
||||||
|
|
||||||
public pinnedControlBtn: HTMLButtonElement;
|
private pinnedControlBtn: HTMLButtonElement;
|
||||||
|
|
||||||
public goDownBtn: HTMLButtonElement;
|
private goDownBtn: HTMLButtonElement;
|
||||||
public goDownUnreadBadge: HTMLElement;
|
private goDownUnreadBadge: HTMLElement;
|
||||||
public btnScheduled: HTMLButtonElement;
|
private btnScheduled: HTMLButtonElement;
|
||||||
|
|
||||||
public saveDraftDebounced: () => void;
|
private saveDraftDebounced: () => void;
|
||||||
|
|
||||||
public fakeRowsWrapper: HTMLDivElement;
|
private fakeRowsWrapper: HTMLDivElement;
|
||||||
private fakePinnedControlBtn: HTMLElement;
|
private fakePinnedControlBtn: HTMLElement;
|
||||||
|
|
||||||
public previousQuery: string;
|
private previousQuery: string;
|
||||||
|
|
||||||
constructor(private chat: Chat, private appMessagesManager: AppMessagesManager, private appDocsManager: AppDocsManager, private appChatsManager: AppChatsManager, private appPeersManager: AppPeersManager, private appWebPagesManager: AppWebPagesManager, private appImManager: AppImManager, private appDraftsManager: AppDraftsManager, private serverTimeManager: ServerTimeManager, private appNotificationsManager: AppNotificationsManager) {
|
constructor(private chat: Chat,
|
||||||
|
private appMessagesManager: AppMessagesManager,
|
||||||
|
private appDocsManager: AppDocsManager,
|
||||||
|
private appChatsManager: AppChatsManager,
|
||||||
|
private appPeersManager: AppPeersManager,
|
||||||
|
private appWebPagesManager: AppWebPagesManager,
|
||||||
|
private appImManager: AppImManager,
|
||||||
|
private appDraftsManager: AppDraftsManager,
|
||||||
|
private serverTimeManager: ServerTimeManager,
|
||||||
|
private appNotificationsManager: AppNotificationsManager,
|
||||||
|
private appEmojiManager: AppEmojiManager
|
||||||
|
) {
|
||||||
this.listenerSetter = new ListenerSetter();
|
this.listenerSetter = new ListenerSetter();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -348,6 +363,7 @@ export default class ChatInput {
|
|||||||
this.newMessageWrapper.append(...[this.btnToggleEmoticons, this.inputMessageContainer, this.btnScheduled, this.attachMenu, this.recordTimeEl, this.fileInput].filter(Boolean));
|
this.newMessageWrapper.append(...[this.btnToggleEmoticons, this.inputMessageContainer, this.btnScheduled, this.attachMenu, this.recordTimeEl, this.fileInput].filter(Boolean));
|
||||||
|
|
||||||
this.rowsWrapper.append(this.replyElements.container);
|
this.rowsWrapper.append(this.replyElements.container);
|
||||||
|
this.emojiHelper = new EmojiHelper(this.rowsWrapper, this);
|
||||||
this.stickersHelper = new StickersHelper(this.rowsWrapper);
|
this.stickersHelper = new StickersHelper(this.rowsWrapper);
|
||||||
this.rowsWrapper.append(this.newMessageWrapper);
|
this.rowsWrapper.append(this.newMessageWrapper);
|
||||||
|
|
||||||
@ -818,6 +834,9 @@ export default class ChatInput {
|
|||||||
}
|
}
|
||||||
}); */
|
}); */
|
||||||
this.listenerSetter.add(this.messageInput, 'input', this.onMessageInput);
|
this.listenerSetter.add(this.messageInput, 'input', this.onMessageInput);
|
||||||
|
this.listenerSetter.add(this.messageInput, 'keyup', () => {
|
||||||
|
this.checkAutocomplete();
|
||||||
|
});
|
||||||
|
|
||||||
if(this.chat.type === 'chat' || this.chat.type === 'discussion') {
|
if(this.chat.type === 'chat' || this.chat.type === 'discussion') {
|
||||||
this.listenerSetter.add(this.messageInput, 'focusin', () => {
|
this.listenerSetter.add(this.messageInput, 'focusin', () => {
|
||||||
@ -1029,10 +1048,10 @@ export default class ChatInput {
|
|||||||
const {value: richValue, entities: markdownEntities, caretPos} = getRichValueWithCaret(this.messageInputField.input);
|
const {value: richValue, entities: markdownEntities, caretPos} = getRichValueWithCaret(this.messageInputField.input);
|
||||||
|
|
||||||
//const entities = RichTextProcessor.parseEntities(value);
|
//const entities = RichTextProcessor.parseEntities(value);
|
||||||
const value = RichTextProcessor.parseMarkdown(richValue, markdownEntities);
|
const value = RichTextProcessor.parseMarkdown(richValue, markdownEntities, true);
|
||||||
const entities = RichTextProcessor.mergeEntities(markdownEntities, RichTextProcessor.parseEntities(value));
|
const entities = RichTextProcessor.mergeEntities(markdownEntities, RichTextProcessor.parseEntities(value));
|
||||||
|
|
||||||
this.chat.log('messageInput entities', richValue, value, markdownEntities, caretPos);
|
//this.chat.log('messageInput entities', richValue, value, markdownEntities, caretPos);
|
||||||
|
|
||||||
if(this.stickersHelper &&
|
if(this.stickersHelper &&
|
||||||
rootScope.settings.stickers.suggest &&
|
rootScope.settings.stickers.suggest &&
|
||||||
@ -1119,99 +1138,132 @@ export default class ChatInput {
|
|||||||
this.saveDraftDebounced();
|
this.saveDraftDebounced();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.checkAutocomplete(richValue, caretPos);
|
||||||
|
|
||||||
this.updateSendBtn();
|
this.updateSendBtn();
|
||||||
};
|
};
|
||||||
|
|
||||||
private checkAutocomplete(value: string, markdownEntities: MessageEntity[], entities: MessageEntity[]) {
|
public onEmojiSelected = (emoji: string, autocomplete: boolean) => {
|
||||||
const matches = value.match(ChatInput.AUTO_COMPLETE_REG_EXP);
|
if(autocomplete) {
|
||||||
if(matches) {
|
const {value: fullValue, caretPos} = getRichValueWithCaret(this.messageInput);
|
||||||
if(this.previousQuery == matches[0]) {
|
const pos = caretPos >= 0 ? caretPos : fullValue.length;
|
||||||
return
|
const suffix = fullValue.substr(pos);
|
||||||
}
|
const prefix = fullValue.substr(0, pos);
|
||||||
this.previousQuery = matches[0]
|
const matches = prefix.match(ChatInput.AUTO_COMPLETE_REG_EXP);
|
||||||
var query = searchIndexManager.cleanSearchText(matches[3])
|
console.log(matches);
|
||||||
|
|
||||||
/* if (matches[2] == '@') { // mentions
|
const idx = matches.index + matches[1].length;
|
||||||
if (this.mentions && this.mentions.index) {
|
|
||||||
if (query.length) {
|
//const str =
|
||||||
var foundObject = SearchIndexManager.search(query, this.mentions.index)
|
|
||||||
var foundUsers = []
|
/* var newValuePrefix
|
||||||
var user
|
if(matches && matches[0]) {
|
||||||
for (var i = 0, length = this.mentions.users.length; i < length; i++) {
|
newValuePrefix = prefix.substr(0, matches.index) + ':' + emoji[1] + ':'
|
||||||
user = this.mentions.users[i]
|
} else {
|
||||||
if (foundObject[user.id]) {
|
newValuePrefix = prefix + ':' + emoji[1] + ':'
|
||||||
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] == '/') { // commands
|
|
||||||
if (this.commands && this.commands.index) {
|
|
||||||
if (query.length) {
|
|
||||||
var foundObject = SearchIndexManager.search(query, this.commands.index)
|
|
||||||
var foundCommands = []
|
|
||||||
var command
|
|
||||||
for (var i = 0, length = this.commands.list.length; i < length; i++) {
|
|
||||||
command = this.commands.list[i]
|
|
||||||
if (foundObject[command.value]) {
|
|
||||||
foundCommands.push(command)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
var foundCommands = this.commands.list
|
|
||||||
}
|
|
||||||
if (foundCommands.length) {
|
|
||||||
this.showCommandsSuggestions(foundCommands)
|
|
||||||
} else {
|
|
||||||
this.hideSuggestions()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.hideSuggestions()
|
|
||||||
}
|
|
||||||
} else *//* if(matches[2] === ':') { // emoji
|
|
||||||
if(value.match(/^\s*:(.+):\s*$/)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
EmojiHelper.getPopularEmoji((function (popular) {
|
|
||||||
if (query.length) {
|
|
||||||
var found = EmojiHelper.searchEmojis(query)
|
|
||||||
if (found.length) {
|
|
||||||
var popularFound = [],
|
|
||||||
code
|
|
||||||
var pos
|
|
||||||
for (var i = 0, len = popular.length; i < len; i++) {
|
|
||||||
code = popular[i].code
|
|
||||||
pos = found.indexOf(code)
|
|
||||||
if (pos >= 0) {
|
|
||||||
popularFound.push(code)
|
|
||||||
found.splice(pos, 1)
|
|
||||||
if (!found.length) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.showEmojiSuggestions(popularFound.concat(found))
|
|
||||||
} else {
|
|
||||||
this.hideSuggestions()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
this.showEmojiSuggestions(popular)
|
|
||||||
}
|
|
||||||
}).bind(this))
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
delete this.previousQuery
|
if(suffix.length) {
|
||||||
this.hideSuggestions() */
|
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)
|
||||||
|
} */
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private checkAutocomplete(value?: string, caretPos?: number) {
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(value === undefined) {
|
||||||
|
const r = getRichValueWithCaret(this.messageInputField.input, false);
|
||||||
|
value = r.value;
|
||||||
|
caretPos = r.caretPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(caretPos === -1) {
|
||||||
|
caretPos = value.length;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.previousQuery === matches[0]) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.previousQuery = matches[0];
|
||||||
|
//let query = cleanSearchText(matches[2]);
|
||||||
|
//const firstChar = matches[2][0];
|
||||||
|
|
||||||
|
//console.log('autocomplete matches', matches);
|
||||||
|
|
||||||
|
/*if (matches[2] == '@') { // mentions
|
||||||
|
if (this.mentions && this.mentions.index) {
|
||||||
|
if (query.length) {
|
||||||
|
var foundObject = SearchIndexManager.search(query, this.mentions.index)
|
||||||
|
var foundUsers = []
|
||||||
|
var user
|
||||||
|
for (var i = 0, length = this.mentions.users.length; i < length; i++) {
|
||||||
|
user = this.mentions.users[i]
|
||||||
|
if (foundObject[user.id]) {
|
||||||
|
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] == '/') { // commands
|
||||||
|
if (this.commands && this.commands.index) {
|
||||||
|
if (query.length) {
|
||||||
|
var foundObject = SearchIndexManager.search(query, this.commands.index)
|
||||||
|
var foundCommands = []
|
||||||
|
var command
|
||||||
|
for (var i = 0, length = this.commands.list.length; i < length; i++) {
|
||||||
|
command = this.commands.list[i]
|
||||||
|
if (foundObject[command.value]) {
|
||||||
|
foundCommands.push(command)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var foundCommands = this.commands.list
|
||||||
|
}
|
||||||
|
if (foundCommands.length) {
|
||||||
|
this.showCommandsSuggestions(foundCommands)
|
||||||
|
} else {
|
||||||
|
this.hideSuggestions()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.hideSuggestions()
|
||||||
|
}
|
||||||
|
} else *//* if(firstChar === ':') */ { // emoji
|
||||||
|
if(value.match(/^\s*:(.+):\s*$/)) {
|
||||||
|
this.emojiHelper.toggle(true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.appEmojiManager.getBothEmojiKeywords().then(() => {
|
||||||
|
const emojis = this.appEmojiManager.searchEmojis(matches[2]);
|
||||||
|
this.emojiHelper.renderEmojis(emojis);
|
||||||
|
//console.log(emojis);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,8 +17,92 @@ import { putPreloader } from "../../misc";
|
|||||||
import Scrollable from "../../scrollable";
|
import Scrollable from "../../scrollable";
|
||||||
import StickyIntersector from "../../stickyIntersector";
|
import StickyIntersector from "../../stickyIntersector";
|
||||||
|
|
||||||
|
const loadedURLs: Set<string> = new Set();
|
||||||
|
export function appendEmoji(emoji: string, container: HTMLElement, prepend = false/* , unified = false */) {
|
||||||
|
//const emoji = details.unified;
|
||||||
|
//const emoji = (details.unified as string).split('-')
|
||||||
|
//.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
|
||||||
|
|
||||||
|
const spanEmoji = document.createElement('span');
|
||||||
|
spanEmoji.classList.add('super-emoji');
|
||||||
|
|
||||||
|
let kek: string;
|
||||||
|
/* if(unified) {
|
||||||
|
kek = RichTextProcessor.wrapRichText('_', {
|
||||||
|
entities: [{
|
||||||
|
_: 'messageEntityEmoji',
|
||||||
|
offset: 0,
|
||||||
|
length: emoji.split('-').length,
|
||||||
|
unicode: emoji
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
} else { */
|
||||||
|
kek = RichTextProcessor.wrapEmojiText(emoji);
|
||||||
|
//}
|
||||||
|
|
||||||
|
/* if(!kek.includes('emoji')) {
|
||||||
|
console.log(emoji, kek, spanEmoji, emoji.length, new TextEncoder().encode(emoji), emojiUnicode(emoji));
|
||||||
|
return;
|
||||||
|
} */
|
||||||
|
|
||||||
|
//console.log(kek);
|
||||||
|
|
||||||
|
spanEmoji.innerHTML = kek;
|
||||||
|
|
||||||
|
if(spanEmoji.children.length > 1) {
|
||||||
|
const first = spanEmoji.firstElementChild;
|
||||||
|
spanEmoji.innerHTML = '';
|
||||||
|
spanEmoji.append(first);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(spanEmoji.firstElementChild && !RichTextProcessor.emojiSupported) {
|
||||||
|
const image = spanEmoji.firstElementChild as HTMLImageElement;
|
||||||
|
image.setAttribute('loading', 'lazy');
|
||||||
|
|
||||||
|
const url = image.src;
|
||||||
|
if(!loadedURLs.has(url)) {
|
||||||
|
const placeholder = document.createElement('span');
|
||||||
|
placeholder.classList.add('emoji-placeholder');
|
||||||
|
|
||||||
|
if(rootScope.settings.animationsEnabled) {
|
||||||
|
image.style.opacity = '0';
|
||||||
|
placeholder.style.opacity = '1';
|
||||||
|
}
|
||||||
|
|
||||||
|
image.addEventListener('load', () => {
|
||||||
|
fastRaf(() => {
|
||||||
|
if(rootScope.settings.animationsEnabled) {
|
||||||
|
image.style.opacity = '';
|
||||||
|
placeholder.style.opacity = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
spanEmoji.classList.remove('empty');
|
||||||
|
|
||||||
|
loadedURLs.add(url);
|
||||||
|
});
|
||||||
|
}, {once: true});
|
||||||
|
|
||||||
|
spanEmoji.append(placeholder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement;
|
||||||
|
//spanEmoji.setAttribute('emoji', emoji);
|
||||||
|
if(prepend) container.prepend(spanEmoji);
|
||||||
|
else container.appendChild(spanEmoji);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getEmojiFromElement(element: HTMLElement) {
|
||||||
|
if(element.nodeType === 3) return element.nodeValue;
|
||||||
|
if(element.tagName === 'SPAN' && !element.classList.contains('emoji')) {
|
||||||
|
element = element.firstElementChild as HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return element.getAttribute('alt') || element.innerText;
|
||||||
|
}
|
||||||
|
|
||||||
export default class EmojiTab implements EmoticonsTab {
|
export default class EmojiTab implements EmoticonsTab {
|
||||||
public content: HTMLElement;
|
private content: HTMLElement;
|
||||||
|
|
||||||
private recent: string[] = [];
|
private recent: string[] = [];
|
||||||
private recentItemsDiv: HTMLElement;
|
private recentItemsDiv: HTMLElement;
|
||||||
@ -26,8 +110,6 @@ export default class EmojiTab implements EmoticonsTab {
|
|||||||
private scroll: Scrollable;
|
private scroll: Scrollable;
|
||||||
private stickyIntersector: StickyIntersector;
|
private stickyIntersector: StickyIntersector;
|
||||||
|
|
||||||
private loadedURLs: Set<string> = new Set();
|
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.content = document.getElementById('content-emoji') as HTMLDivElement;
|
this.content = document.getElementById('content-emoji') as HTMLDivElement;
|
||||||
|
|
||||||
@ -84,7 +166,7 @@ export default class EmojiTab implements EmoticonsTab {
|
|||||||
titleDiv.append(i18n(category));
|
titleDiv.append(i18n(category));
|
||||||
|
|
||||||
const itemsDiv = document.createElement('div');
|
const itemsDiv = document.createElement('div');
|
||||||
itemsDiv.classList.add('category-items');
|
itemsDiv.classList.add('super-emojis');
|
||||||
|
|
||||||
div.append(titleDiv, itemsDiv);
|
div.append(titleDiv, itemsDiv);
|
||||||
|
|
||||||
@ -95,7 +177,7 @@ export default class EmojiTab implements EmoticonsTab {
|
|||||||
|
|
||||||
emoji = emoji.split('-').reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
|
emoji = emoji.split('-').reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
|
||||||
|
|
||||||
this.appendEmoji(emoji/* .replace(/[\ufe0f\u2640\u2642\u2695]/g, '') */, itemsDiv, false/* , false */);
|
appendEmoji(emoji/* .replace(/[\ufe0f\u2640\u2642\u2695]/g, '') */, itemsDiv, false/* , false */);
|
||||||
|
|
||||||
/* if(category === 'Smileys & Emotion') {
|
/* if(category === 'Smileys & Emotion') {
|
||||||
console.log('appended emoji', emoji, itemsDiv.children[itemsDiv.childElementCount - 1].innerHTML, emojiUnicode(emoji));
|
console.log('appended emoji', emoji, itemsDiv.children[itemsDiv.childElementCount - 1].innerHTML, emojiUnicode(emoji));
|
||||||
@ -125,9 +207,9 @@ export default class EmojiTab implements EmoticonsTab {
|
|||||||
]).then(() => {
|
]).then(() => {
|
||||||
preloader.remove();
|
preloader.remove();
|
||||||
|
|
||||||
this.recentItemsDiv = divs['Emoji.Recent'].querySelector('.category-items');
|
this.recentItemsDiv = divs['Emoji.Recent'].querySelector('.super-emojis');
|
||||||
for(const emoji of this.recent) {
|
for(const emoji of this.recent) {
|
||||||
this.appendEmoji(emoji, this.recentItemsDiv);
|
appendEmoji(emoji, this.recentItemsDiv);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.recentItemsDiv.parentElement.classList.toggle('hide', !this.recent.length);
|
this.recentItemsDiv.parentElement.classList.toggle('hide', !this.recent.length);
|
||||||
@ -151,95 +233,12 @@ export default class EmojiTab implements EmoticonsTab {
|
|||||||
this.init = null;
|
this.init = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private appendEmoji(emoji: string, container: HTMLElement, prepend = false/* , unified = false */) {
|
|
||||||
//const emoji = details.unified;
|
|
||||||
//const emoji = (details.unified as string).split('-')
|
|
||||||
//.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
|
|
||||||
|
|
||||||
const spanEmoji = document.createElement('span');
|
|
||||||
spanEmoji.classList.add('category-item');
|
|
||||||
|
|
||||||
let kek: string;
|
|
||||||
/* if(unified) {
|
|
||||||
kek = RichTextProcessor.wrapRichText('_', {
|
|
||||||
entities: [{
|
|
||||||
_: 'messageEntityEmoji',
|
|
||||||
offset: 0,
|
|
||||||
length: emoji.split('-').length,
|
|
||||||
unicode: emoji
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
} else { */
|
|
||||||
kek = RichTextProcessor.wrapEmojiText(emoji);
|
|
||||||
//}
|
|
||||||
|
|
||||||
/* if(!kek.includes('emoji')) {
|
|
||||||
console.log(emoji, kek, spanEmoji, emoji.length, new TextEncoder().encode(emoji), emojiUnicode(emoji));
|
|
||||||
return;
|
|
||||||
} */
|
|
||||||
|
|
||||||
//console.log(kek);
|
|
||||||
|
|
||||||
spanEmoji.innerHTML = kek;
|
|
||||||
|
|
||||||
if(spanEmoji.children.length > 1) {
|
|
||||||
const first = spanEmoji.firstElementChild;
|
|
||||||
spanEmoji.innerHTML = '';
|
|
||||||
spanEmoji.append(first);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(spanEmoji.firstElementChild && !RichTextProcessor.emojiSupported) {
|
|
||||||
const image = spanEmoji.firstElementChild as HTMLImageElement;
|
|
||||||
image.setAttribute('loading', 'lazy');
|
|
||||||
|
|
||||||
const url = image.src;
|
|
||||||
if(!this.loadedURLs.has(url)) {
|
|
||||||
const placeholder = document.createElement('span');
|
|
||||||
placeholder.classList.add('emoji-placeholder');
|
|
||||||
|
|
||||||
if(rootScope.settings.animationsEnabled) {
|
|
||||||
image.style.opacity = '0';
|
|
||||||
placeholder.style.opacity = '1';
|
|
||||||
}
|
|
||||||
|
|
||||||
image.addEventListener('load', () => {
|
|
||||||
fastRaf(() => {
|
|
||||||
if(rootScope.settings.animationsEnabled) {
|
|
||||||
image.style.opacity = '';
|
|
||||||
placeholder.style.opacity = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
spanEmoji.classList.remove('empty');
|
|
||||||
|
|
||||||
this.loadedURLs.add(url);
|
|
||||||
});
|
|
||||||
}, {once: true});
|
|
||||||
|
|
||||||
spanEmoji.append(placeholder);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//spanEmoji = spanEmoji.firstElementChild as HTMLSpanElement;
|
|
||||||
//spanEmoji.setAttribute('emoji', emoji);
|
|
||||||
if(prepend) container.prepend(spanEmoji);
|
|
||||||
else container.appendChild(spanEmoji);
|
|
||||||
}
|
|
||||||
|
|
||||||
private getEmojiFromElement(element: HTMLElement) {
|
|
||||||
if(element.nodeType === 3) return element.nodeValue;
|
|
||||||
if(element.tagName === 'SPAN' && !element.classList.contains('emoji')) {
|
|
||||||
element = element.firstElementChild as HTMLElement;
|
|
||||||
}
|
|
||||||
|
|
||||||
return element.getAttribute('alt') || element.innerText;
|
|
||||||
}
|
|
||||||
|
|
||||||
onContentClick = (e: MouseEvent) => {
|
onContentClick = (e: MouseEvent) => {
|
||||||
let target = e.target as HTMLElement;
|
let target = e.target as HTMLElement;
|
||||||
//if(target.tagName !== 'SPAN') return;
|
//if(target.tagName !== 'SPAN') return;
|
||||||
|
|
||||||
if(target.tagName === 'SPAN' && !target.classList.contains('emoji')) {
|
if(target.tagName === 'SPAN' && !target.classList.contains('emoji')) {
|
||||||
target = findUpClassName(target, 'category-item');
|
target = findUpClassName(target, 'super-emoji');
|
||||||
if(!target) {
|
if(!target) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -253,15 +252,15 @@ export default class EmojiTab implements EmoticonsTab {
|
|||||||
target.outerHTML;
|
target.outerHTML;
|
||||||
|
|
||||||
// Recent
|
// Recent
|
||||||
const emoji = this.getEmojiFromElement(target);
|
const emoji = getEmojiFromElement(target);
|
||||||
(Array.from(this.recentItemsDiv.children) as HTMLElement[]).forEach((el, idx) => {
|
(Array.from(this.recentItemsDiv.children) as HTMLElement[]).forEach((el, idx) => {
|
||||||
const _emoji = this.getEmojiFromElement(el);
|
const _emoji = getEmojiFromElement(el);
|
||||||
if(emoji === _emoji) {
|
if(emoji === _emoji) {
|
||||||
el.remove();
|
el.remove();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
const scrollHeight = this.recentItemsDiv.scrollHeight;
|
//const scrollHeight = this.recentItemsDiv.scrollHeight;
|
||||||
this.appendEmoji(emoji, this.recentItemsDiv, true);
|
appendEmoji(emoji, this.recentItemsDiv, true);
|
||||||
|
|
||||||
this.recent.findAndSplice(e => e === emoji);
|
this.recent.findAndSplice(e => e === emoji);
|
||||||
this.recent.unshift(emoji);
|
this.recent.unshift(emoji);
|
||||||
|
@ -12,7 +12,7 @@ import apiManager from "../../../lib/mtproto/mtprotoworker";
|
|||||||
import appDocsManager, {MyDocument} from "../../../lib/appManagers/appDocsManager";
|
import appDocsManager, {MyDocument} from "../../../lib/appManagers/appDocsManager";
|
||||||
|
|
||||||
export default class GifsTab implements EmoticonsTab {
|
export default class GifsTab implements EmoticonsTab {
|
||||||
public content: HTMLElement;
|
private content: HTMLElement;
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.content = document.getElementById('content-gifs');
|
this.content = document.getElementById('content-gifs');
|
||||||
@ -45,4 +45,4 @@ export default class GifsTab implements EmoticonsTab {
|
|||||||
onClose() {
|
onClose() {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,8 @@ import StickyIntersector from "../../stickyIntersector";
|
|||||||
import { wrapSticker } from "../../wrappers";
|
import { wrapSticker } from "../../wrappers";
|
||||||
|
|
||||||
export class SuperStickerRenderer {
|
export class SuperStickerRenderer {
|
||||||
lazyLoadQueue: LazyLoadQueueRepeat;
|
public lazyLoadQueue: LazyLoadQueueRepeat;
|
||||||
animatedDivs: Set<HTMLDivElement> = new Set();
|
private animatedDivs: Set<HTMLDivElement> = new Set();
|
||||||
|
|
||||||
constructor(private regularLazyLoadQueue: LazyLoadQueue, private group: string) {
|
constructor(private regularLazyLoadQueue: LazyLoadQueue, private group: string) {
|
||||||
this.lazyLoadQueue = new LazyLoadQueueRepeat(undefined, (target, visible) => {
|
this.lazyLoadQueue = new LazyLoadQueueRepeat(undefined, (target, visible) => {
|
||||||
@ -35,7 +35,7 @@ export class SuperStickerRenderer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSticker(doc: MyDocument, div?: HTMLDivElement, loadPromises?: Promise<any>[]) {
|
public renderSticker(doc: MyDocument, div?: HTMLDivElement, loadPromises?: Promise<any>[]) {
|
||||||
if(!div) {
|
if(!div) {
|
||||||
div = document.createElement('div');
|
div = document.createElement('div');
|
||||||
div.classList.add('grid-item', 'super-sticker');
|
div.classList.add('grid-item', 'super-sticker');
|
||||||
@ -63,7 +63,7 @@ export class SuperStickerRenderer {
|
|||||||
return div;
|
return div;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkAnimationContainer = (div: HTMLElement, visible: boolean) => {
|
private checkAnimationContainer = (div: HTMLElement, visible: boolean) => {
|
||||||
//console.error('checkAnimationContainer', div, visible);
|
//console.error('checkAnimationContainer', div, visible);
|
||||||
const players = animationIntersector.getAnimations(div);
|
const players = animationIntersector.getAnimations(div);
|
||||||
players.forEach(player => {
|
players.forEach(player => {
|
||||||
@ -75,7 +75,7 @@ export class SuperStickerRenderer {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
processVisibleDiv = (div: HTMLElement) => {
|
private processVisibleDiv = (div: HTMLElement) => {
|
||||||
const docId = div.dataset.docId;
|
const docId = div.dataset.docId;
|
||||||
const doc = appDocsManager.getDoc(docId);
|
const doc = appDocsManager.getDoc(docId);
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ export class SuperStickerRenderer {
|
|||||||
return promise;
|
return promise;
|
||||||
};
|
};
|
||||||
|
|
||||||
processInvisibleDiv = (div: HTMLElement) => {
|
public processInvisibleDiv = (div: HTMLElement) => {
|
||||||
const docId = div.dataset.docId;
|
const docId = div.dataset.docId;
|
||||||
const doc = appDocsManager.getDoc(docId);
|
const doc = appDocsManager.getDoc(docId);
|
||||||
|
|
||||||
@ -121,7 +121,7 @@ export class SuperStickerRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default class StickersTab implements EmoticonsTab {
|
export default class StickersTab implements EmoticonsTab {
|
||||||
public content: HTMLElement;
|
private content: HTMLElement;
|
||||||
private stickersDiv: HTMLElement;
|
private stickersDiv: HTMLElement;
|
||||||
|
|
||||||
private stickerSets: {[id: string]: {
|
private stickerSets: {[id: string]: {
|
||||||
@ -381,4 +381,4 @@ export default class StickersTab implements EmoticonsTab {
|
|||||||
onClose() {
|
onClose() {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
33
src/helpers/cleanSearchText.ts
Normal file
33
src/helpers/cleanSearchText.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/*
|
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*
|
||||||
|
* Originally from:
|
||||||
|
* https://github.com/zhukov/webogram
|
||||||
|
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
|
||||||
|
* https://github.com/zhukov/webogram/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
import Config from "../lib/config";
|
||||||
|
|
||||||
|
const badCharsRe = /[`~!@#$%^&*()\-_=+\[\]\\|{}'";:\/?.>,<]+/g;
|
||||||
|
const trimRe = /^\s+|\s$/g;
|
||||||
|
|
||||||
|
export default function cleanSearchText(text: string, latinize = true) {
|
||||||
|
const hasTag = text.charAt(0) === '%';
|
||||||
|
text = text.replace(badCharsRe, '').replace(trimRe, '');
|
||||||
|
if(latinize) {
|
||||||
|
text = text.replace(/[^A-Za-z0-9]/g, (ch) => {
|
||||||
|
const latinizeCh = Config.LatinizeMap[ch];
|
||||||
|
return latinizeCh !== undefined ? latinizeCh : ch;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
text = text.toLowerCase();
|
||||||
|
if(hasTag) {
|
||||||
|
text = '%' + text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return text;
|
||||||
|
}
|
14
src/helpers/cleanUsername.ts
Normal file
14
src/helpers/cleanUsername.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* https://github.com/morethanwords/tweb
|
||||||
|
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||||
|
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||||
|
*
|
||||||
|
* Originally from:
|
||||||
|
* https://github.com/zhukov/webogram
|
||||||
|
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com>
|
||||||
|
* https://github.com/zhukov/webogram/blob/master/LICENSE
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function cleanUsername(username: string) {
|
||||||
|
return username && username.toLowerCase() || '';
|
||||||
|
}
|
@ -27,7 +27,7 @@ export default function attachListNavigation({list, type, onSelect, once}: {
|
|||||||
return target || list.querySelector('.' + ACTIVE_CLASS_NAME) || list.firstElementChild;
|
return target || list.querySelector('.' + ACTIVE_CLASS_NAME) || list.firstElementChild;
|
||||||
};
|
};
|
||||||
|
|
||||||
const setCurrentTarget = (_target: Element) => {
|
const setCurrentTarget = (_target: Element, scrollTo: boolean) => {
|
||||||
if(target === _target) {
|
if(target === _target) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ export default function attachListNavigation({list, type, onSelect, once}: {
|
|||||||
target = _target;
|
target = _target;
|
||||||
target.classList.add(ACTIVE_CLASS_NAME);
|
target.classList.add(ACTIVE_CLASS_NAME);
|
||||||
|
|
||||||
if(hadTarget && scrollable) {
|
if(hadTarget && scrollable && scrollTo) {
|
||||||
fastSmoothScroll(scrollable, target as HTMLElement, 'center', undefined, undefined, undefined, 100, type === 'x' ? 'x' : 'y');
|
fastSmoothScroll(scrollable, target as HTMLElement, 'center', undefined, undefined, undefined, 100, type === 'x' ? 'x' : 'y');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -97,7 +97,7 @@ export default function attachListNavigation({list, type, onSelect, once}: {
|
|||||||
if(list.childElementCount > 1) {
|
if(list.childElementCount > 1) {
|
||||||
let currentTarget = getCurrentTarget();
|
let currentTarget = getCurrentTarget();
|
||||||
currentTarget = handleArrowKey(currentTarget, e.key as any);
|
currentTarget = handleArrowKey(currentTarget, e.key as any);
|
||||||
setCurrentTarget(currentTarget);
|
setCurrentTarget(currentTarget, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -112,7 +112,7 @@ export default function attachListNavigation({list, type, onSelect, once}: {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentTarget(target);
|
setCurrentTarget(target, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClick = (e: Event) => {
|
const onClick = (e: Event) => {
|
||||||
@ -123,7 +123,7 @@ export default function attachListNavigation({list, type, onSelect, once}: {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setCurrentTarget(target);
|
setCurrentTarget(target, false);
|
||||||
fireSelect(getCurrentTarget());
|
fireSelect(getCurrentTarget());
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -142,7 +142,7 @@ export default function attachListNavigation({list, type, onSelect, once}: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const resetTarget = () => {
|
const resetTarget = () => {
|
||||||
setCurrentTarget(list.firstElementChild);
|
setCurrentTarget(list.firstElementChild, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
resetTarget();
|
resetTarget();
|
||||||
|
@ -15,7 +15,7 @@ const set = (elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoE
|
|||||||
export default function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string, callback?: (err?: Event) => void, useCache = true): boolean {
|
export default function renderImageFromUrl(elem: HTMLElement | HTMLImageElement | SVGImageElement | HTMLVideoElement, url: string, callback?: (err?: Event) => void, useCache = true): boolean {
|
||||||
if(!url) {
|
if(!url) {
|
||||||
console.error('renderImageFromUrl: no url?', elem, url);
|
console.error('renderImageFromUrl: no url?', elem, url);
|
||||||
//callback && callback();
|
callback && callback();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
185
src/lib/appManagers/appEmojiManager.ts
Normal file
185
src/lib/appManagers/appEmojiManager.ts
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
import App from "../../config/app";
|
||||||
|
import { MOUNT_CLASS_TO } from "../../config/debug";
|
||||||
|
import { validateInitObject } from "../../helpers/object";
|
||||||
|
import I18n from "../langPack";
|
||||||
|
import { isObject } from "../mtproto/bin_utils";
|
||||||
|
import apiManager from "../mtproto/mtprotoworker";
|
||||||
|
import SearchIndex from "../searchIndex";
|
||||||
|
import sessionStorage from "../sessionStorage";
|
||||||
|
|
||||||
|
type EmojiLangPack = {
|
||||||
|
keywords: {
|
||||||
|
[keyword: string]: string[],
|
||||||
|
},
|
||||||
|
version: number,
|
||||||
|
langCode: string
|
||||||
|
};
|
||||||
|
|
||||||
|
const EMOJI_LANG_PACK: EmojiLangPack = {
|
||||||
|
keywords: {},
|
||||||
|
version: 0,
|
||||||
|
langCode: App.langPackCode
|
||||||
|
};
|
||||||
|
|
||||||
|
export class AppEmojiManager {
|
||||||
|
private keywordLangPacks: {
|
||||||
|
[langCode: string]: EmojiLangPack
|
||||||
|
} = {};
|
||||||
|
|
||||||
|
private index: SearchIndex<EmojiLangPack['keywords'][keyof EmojiLangPack['keywords']]>;
|
||||||
|
private indexedLangPacks: {[langCode: string]: boolean} = {};
|
||||||
|
|
||||||
|
private getKeywordsPromises: {[langCode: string]: Promise<EmojiLangPack>} = {};
|
||||||
|
|
||||||
|
/* public getPopularEmoji() {
|
||||||
|
return sessionStorage.get('emojis_popular').then(popEmojis => {
|
||||||
|
var result = []
|
||||||
|
if (popEmojis && popEmojis.length) {
|
||||||
|
for (var i = 0, len = popEmojis.length; i < len; i++) {
|
||||||
|
result.push({code: popEmojis[i][0], rate: popEmojis[i][1]})
|
||||||
|
}
|
||||||
|
callback(result)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessionStorage.get('emojis_recent').then(recentEmojis => {
|
||||||
|
recentEmojis = recentEmojis || popular || []
|
||||||
|
var shortcut
|
||||||
|
var code
|
||||||
|
for (var i = 0, len = recentEmojis.length; i < len; i++) {
|
||||||
|
shortcut = recentEmojis[i]
|
||||||
|
if (Array.isArray(shortcut)) {
|
||||||
|
shortcut = shortcut[0]
|
||||||
|
}
|
||||||
|
if (shortcut && typeof shortcut === 'string') {
|
||||||
|
if (shortcut.charAt(0) == ':') {
|
||||||
|
shortcut = shortcut.substr(1, shortcut.length - 2)
|
||||||
|
}
|
||||||
|
if (code = shortcuts[shortcut]) {
|
||||||
|
result.push({code: code, rate: 1})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callback(result)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function pushPopularEmoji (code) {
|
||||||
|
getPopularEmoji(function (popularEmoji) {
|
||||||
|
var exists = false
|
||||||
|
var count = popularEmoji.length
|
||||||
|
var result = []
|
||||||
|
for (var i = 0; i < count; i++) {
|
||||||
|
if (popularEmoji[i].code == code) {
|
||||||
|
exists = true
|
||||||
|
popularEmoji[i].rate++
|
||||||
|
}
|
||||||
|
result.push([popularEmoji[i].code, popularEmoji[i].rate])
|
||||||
|
}
|
||||||
|
if (exists) {
|
||||||
|
result.sort(function (a, b) {
|
||||||
|
return b[1] - a[1]
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
if (result.length > 41) {
|
||||||
|
result = result.slice(0, 41)
|
||||||
|
}
|
||||||
|
result.push([code, 1])
|
||||||
|
}
|
||||||
|
ConfigStorage.set({emojis_popular: result})
|
||||||
|
})
|
||||||
|
} */
|
||||||
|
|
||||||
|
public getEmojiKeywords(langCode: string = App.langPackCode) {
|
||||||
|
const promise = this.getKeywordsPromises[langCode];
|
||||||
|
if(promise) {
|
||||||
|
return promise;
|
||||||
|
}
|
||||||
|
|
||||||
|
const storageKey: any = 'emojiKeywords_' + langCode;
|
||||||
|
return this.getKeywordsPromises[langCode] = sessionStorage.get(storageKey).then((pack: EmojiLangPack) => {
|
||||||
|
if(!isObject(pack)) {
|
||||||
|
pack = {} as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
validateInitObject(EMOJI_LANG_PACK, pack);
|
||||||
|
|
||||||
|
// important
|
||||||
|
pack.langCode = langCode;
|
||||||
|
this.keywordLangPacks[langCode] = pack;
|
||||||
|
|
||||||
|
return apiManager.invokeApi('messages.getEmojiKeywordsDifference', {
|
||||||
|
lang_code: pack.langCode,
|
||||||
|
from_version: pack.version
|
||||||
|
}).then((keywordsDifference) => {
|
||||||
|
pack.version = keywordsDifference.version;
|
||||||
|
|
||||||
|
const packKeywords = pack.keywords;
|
||||||
|
const keywords = keywordsDifference.keywords;
|
||||||
|
for(let i = 0, length = keywords.length; i < length; ++i) {
|
||||||
|
const {keyword, emoticons} = keywords[i];
|
||||||
|
packKeywords[keyword] = emoticons;
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionStorage.set({
|
||||||
|
[storageKey]: pack
|
||||||
|
});
|
||||||
|
|
||||||
|
return pack;
|
||||||
|
}, () => {
|
||||||
|
return pack;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public getBothEmojiKeywords() {
|
||||||
|
const promises: ReturnType<AppEmojiManager['getEmojiKeywords']>[] = [
|
||||||
|
this.getEmojiKeywords()
|
||||||
|
];
|
||||||
|
|
||||||
|
if(I18n.lastRequestedLangCode !== App.langPackCode) {
|
||||||
|
promises.push(this.getEmojiKeywords(I18n.lastRequestedLangCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
|
public indexEmojis() {
|
||||||
|
if(!this.index) {
|
||||||
|
this.index = new SearchIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const langCode in this.keywordLangPacks) {
|
||||||
|
if(this.indexedLangPacks[langCode]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const pack = this.keywordLangPacks[langCode];
|
||||||
|
const keywords = pack.keywords;
|
||||||
|
|
||||||
|
for(const keyword in keywords) {
|
||||||
|
const emoticons = keywords[keyword];
|
||||||
|
this.index.indexObject(emoticons, keyword);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.indexedLangPacks[langCode] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public searchEmojis(q: string) {
|
||||||
|
this.indexEmojis();
|
||||||
|
|
||||||
|
//const perf = performance.now();
|
||||||
|
const set = this.index.search(q);
|
||||||
|
const flattened = Array.from(set).reduce((acc, v) => acc.concat(v), []);
|
||||||
|
const emojis = Array.from(new Set(flattened));
|
||||||
|
//console.log('searchEmojis', q, 'time', performance.now() - perf);
|
||||||
|
|
||||||
|
return emojis;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const appEmojiManager = new AppEmojiManager();
|
||||||
|
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.appEmojiManager = appEmojiManager);
|
||||||
|
export default appEmojiManager;
|
@ -56,6 +56,7 @@ import disableTransition from '../../helpers/dom/disableTransition';
|
|||||||
import placeCaretAtEnd from '../../helpers/dom/placeCaretAtEnd';
|
import placeCaretAtEnd from '../../helpers/dom/placeCaretAtEnd';
|
||||||
import replaceContent from '../../helpers/dom/replaceContent';
|
import replaceContent from '../../helpers/dom/replaceContent';
|
||||||
import whichChild from '../../helpers/dom/whichChild';
|
import whichChild from '../../helpers/dom/whichChild';
|
||||||
|
import appEmojiManager from './appEmojiManager';
|
||||||
|
|
||||||
//console.log('appImManager included33!');
|
//console.log('appImManager included33!');
|
||||||
|
|
||||||
@ -726,7 +727,25 @@ export class AppImManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private createNewChat() {
|
private createNewChat() {
|
||||||
const chat = new Chat(this, appChatsManager, appDocsManager, appInlineBotsManager, appMessagesManager, appPeersManager, appPhotosManager, appProfileManager, appStickersManager, appUsersManager, appWebPagesManager, appPollsManager, apiManager, appDraftsManager, serverTimeManager, sessionStorage, appNotificationsManager);
|
const chat = new Chat(this,
|
||||||
|
appChatsManager,
|
||||||
|
appDocsManager,
|
||||||
|
appInlineBotsManager,
|
||||||
|
appMessagesManager,
|
||||||
|
appPeersManager,
|
||||||
|
appPhotosManager,
|
||||||
|
appProfileManager,
|
||||||
|
appStickersManager,
|
||||||
|
appUsersManager,
|
||||||
|
appWebPagesManager,
|
||||||
|
appPollsManager,
|
||||||
|
apiManager,
|
||||||
|
appDraftsManager,
|
||||||
|
serverTimeManager,
|
||||||
|
sessionStorage,
|
||||||
|
appNotificationsManager,
|
||||||
|
appEmojiManager
|
||||||
|
);
|
||||||
|
|
||||||
if(this.chats.length) {
|
if(this.chats.length) {
|
||||||
chat.backgroundEl.append(this.chat.backgroundEl.lastElementChild.cloneNode(true));
|
chat.backgroundEl.append(this.chat.backgroundEl.lastElementChild.cloneNode(true));
|
||||||
|
@ -11,6 +11,8 @@
|
|||||||
|
|
||||||
import { formatPhoneNumber } from "../../components/misc";
|
import { formatPhoneNumber } from "../../components/misc";
|
||||||
import { MOUNT_CLASS_TO } from "../../config/debug";
|
import { MOUNT_CLASS_TO } from "../../config/debug";
|
||||||
|
import cleanSearchText from "../../helpers/cleanSearchText";
|
||||||
|
import cleanUsername from "../../helpers/cleanUsername";
|
||||||
import { tsNow } from "../../helpers/date";
|
import { tsNow } from "../../helpers/date";
|
||||||
import { safeReplaceObject, isObject } from "../../helpers/object";
|
import { safeReplaceObject, isObject } from "../../helpers/object";
|
||||||
import { InputUser, Update, User as MTUser, UserStatus } from "../../layer";
|
import { InputUser, Update, User as MTUser, UserStatus } from "../../layer";
|
||||||
@ -21,7 +23,7 @@ import { REPLIES_PEER_ID } from "../mtproto/mtproto_config";
|
|||||||
import serverTimeManager from "../mtproto/serverTimeManager";
|
import serverTimeManager from "../mtproto/serverTimeManager";
|
||||||
import { RichTextProcessor } from "../richtextprocessor";
|
import { RichTextProcessor } from "../richtextprocessor";
|
||||||
import rootScope from "../rootScope";
|
import rootScope from "../rootScope";
|
||||||
import searchIndexManager from "../searchIndexManager";
|
import SearchIndex from "../searchIndex";
|
||||||
import apiUpdatesManager from "./apiUpdatesManager";
|
import apiUpdatesManager from "./apiUpdatesManager";
|
||||||
import appChatsManager from "./appChatsManager";
|
import appChatsManager from "./appChatsManager";
|
||||||
import appPeersManager from "./appPeersManager";
|
import appPeersManager from "./appPeersManager";
|
||||||
@ -36,7 +38,7 @@ export class AppUsersManager {
|
|||||||
|
|
||||||
private users: {[userId: number]: User} = {};
|
private users: {[userId: number]: User} = {};
|
||||||
private usernames: {[username: string]: number} = {};
|
private usernames: {[username: string]: number} = {};
|
||||||
private contactsIndex = searchIndexManager.createIndex();
|
private contactsIndex = new SearchIndex<number>();
|
||||||
private contactsFillPromise: Promise<Set<number>>;
|
private contactsFillPromise: Promise<Set<number>>;
|
||||||
private contactsList: Set<number> = new Set();
|
private contactsList: Set<number> = new Set();
|
||||||
private updatedContactsList = false;
|
private updatedContactsList = false;
|
||||||
@ -110,7 +112,7 @@ export class AppUsersManager {
|
|||||||
|
|
||||||
rootScope.on('language_change', (e) => {
|
rootScope.on('language_change', (e) => {
|
||||||
const userId = this.getSelf().id;
|
const userId = this.getSelf().id;
|
||||||
searchIndexManager.indexObject(userId, this.getUserSearchText(userId), this.contactsIndex);
|
this.contactsIndex.indexObject(userId, this.getUserSearchText(userId));
|
||||||
});
|
});
|
||||||
|
|
||||||
appStateManager.getState().then((state) => {
|
appStateManager.getState().then((state) => {
|
||||||
@ -207,7 +209,7 @@ export class AppUsersManager {
|
|||||||
|
|
||||||
public pushContact(userId: number) {
|
public pushContact(userId: number) {
|
||||||
this.contactsList.add(userId);
|
this.contactsList.add(userId);
|
||||||
searchIndexManager.indexObject(userId, this.getUserSearchText(userId), this.contactsIndex);
|
this.contactsIndex.indexObject(userId, this.getUserSearchText(userId));
|
||||||
appStateManager.requestPeer(userId, 'contacts');
|
appStateManager.requestPeer(userId, 'contacts');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -233,8 +235,8 @@ export class AppUsersManager {
|
|||||||
return this.fillContacts().then(_contactsList => {
|
return this.fillContacts().then(_contactsList => {
|
||||||
let contactsList = [..._contactsList];
|
let contactsList = [..._contactsList];
|
||||||
if(query) {
|
if(query) {
|
||||||
const results = searchIndexManager.search(query, this.contactsIndex);
|
const results = this.contactsIndex.search(query);
|
||||||
const filteredContactsList = [...contactsList].filter(id => !!results[id]);
|
const filteredContactsList = [...contactsList].filter(id => results.has(id));
|
||||||
|
|
||||||
contactsList = filteredContactsList;
|
contactsList = filteredContactsList;
|
||||||
}
|
}
|
||||||
@ -288,9 +290,9 @@ export class AppUsersManager {
|
|||||||
|
|
||||||
public testSelfSearch(query: string) {
|
public testSelfSearch(query: string) {
|
||||||
const user = this.getSelf();
|
const user = this.getSelf();
|
||||||
const index = searchIndexManager.createIndex();
|
const index = new SearchIndex();
|
||||||
searchIndexManager.indexObject(user.id, this.getUserSearchText(user.id), index);
|
index.indexObject(user.id, this.getUserSearchText(user.id));
|
||||||
return !!searchIndexManager.search(query, index)[user.id];
|
return index.search(query).has(user.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public saveApiUsers(apiUsers: any[], override?: boolean) {
|
public saveApiUsers(apiUsers: any[], override?: boolean) {
|
||||||
@ -320,11 +322,11 @@ export class AppUsersManager {
|
|||||||
|
|
||||||
const fullName = user.first_name + ' ' + (user.last_name || '');
|
const fullName = user.first_name + ' ' + (user.last_name || '');
|
||||||
if(user.username) {
|
if(user.username) {
|
||||||
const searchUsername = searchIndexManager.cleanUsername(user.username);
|
const searchUsername = cleanUsername(user.username);
|
||||||
this.usernames[searchUsername] = userId;
|
this.usernames[searchUsername] = userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
user.sortName = user.pFlags.deleted ? '' : searchIndexManager.cleanSearchText(fullName, false);
|
user.sortName = user.pFlags.deleted ? '' : cleanSearchText(fullName, false);
|
||||||
|
|
||||||
user.initials = RichTextProcessor.getAbbreviation(fullName);
|
user.initials = RichTextProcessor.getAbbreviation(fullName);
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ import MTTransport from "./transports/transport";
|
|||||||
export class NetworkerFactory {
|
export class NetworkerFactory {
|
||||||
public updatesProcessor: (obj: any) => void = null;
|
public updatesProcessor: (obj: any) => void = null;
|
||||||
public onConnectionStatusChange: (info: ConnectionStatusChange) => void = null;
|
public onConnectionStatusChange: (info: ConnectionStatusChange) => void = null;
|
||||||
|
public akStopped = false;
|
||||||
|
|
||||||
public setUpdatesProcessor(callback: (obj: any) => void) {
|
public setUpdatesProcessor(callback: (obj: any) => void) {
|
||||||
this.updatesProcessor = callback;
|
this.updatesProcessor = callback;
|
||||||
@ -25,6 +26,17 @@ export class NetworkerFactory {
|
|||||||
//console.log('NetworkerFactory: creating new instance of MTPNetworker:', dcId, options);
|
//console.log('NetworkerFactory: creating new instance of MTPNetworker:', dcId, options);
|
||||||
return new MTPNetworker(dcId, authKey, authKeyID, serverSalt, transport, options);
|
return new MTPNetworker(dcId, authKey, authKeyID, serverSalt, transport, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public startAll() {
|
||||||
|
if(this.akStopped) {
|
||||||
|
this.akStopped = false;
|
||||||
|
this.updatesProcessor && this.updatesProcessor({_: 'new_session_created'});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public stopAll() {
|
||||||
|
this.akStopped = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new NetworkerFactory();
|
export default new NetworkerFactory();
|
||||||
|
113
src/lib/mtproto/singleInstance.ts
Normal file
113
src/lib/mtproto/singleInstance.ts
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
import { MOUNT_CLASS_TO } from "../../config/debug";
|
||||||
|
import { nextRandomInt } from "../../helpers/random";
|
||||||
|
import { logger } from "../logger";
|
||||||
|
import rootScope from "../rootScope";
|
||||||
|
import sessionStorage from "../sessionStorage";
|
||||||
|
|
||||||
|
export type AppInstance = {
|
||||||
|
id: number,
|
||||||
|
idle: boolean,
|
||||||
|
time: number
|
||||||
|
};
|
||||||
|
|
||||||
|
export class SingleInstance {
|
||||||
|
private instanceID = nextRandomInt(0xFFFFFFFF);
|
||||||
|
private started = false;
|
||||||
|
private masterInstance = false;
|
||||||
|
private deactivateTimeout: number = 0;
|
||||||
|
private deactivated = false;
|
||||||
|
private initial = false;
|
||||||
|
private log = logger('SI');
|
||||||
|
|
||||||
|
public start() {
|
||||||
|
if(!this.started/* && !Config.Navigator.mobile && !Config.Modes.packed */) {
|
||||||
|
this.started = true
|
||||||
|
|
||||||
|
//IdleManager.start();
|
||||||
|
|
||||||
|
rootScope.addEventListener('idle', this.checkInstance);
|
||||||
|
setInterval(this.checkInstance, 5000);
|
||||||
|
this.checkInstance();
|
||||||
|
|
||||||
|
try {
|
||||||
|
document.documentElement.addEventListener('beforeunload', this.clearInstance);
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearInstance() {
|
||||||
|
if(this.masterInstance && !this.deactivated) {
|
||||||
|
this.log.warn('clear master instance');
|
||||||
|
sessionStorage.delete('xt_instance');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public deactivateInstance = () => {
|
||||||
|
if(this.masterInstance || this.deactivated) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.log('deactivate');
|
||||||
|
this.deactivateTimeout = 0;
|
||||||
|
this.deactivated = true;
|
||||||
|
this.clearInstance();
|
||||||
|
//$modalStack.dismissAll();
|
||||||
|
|
||||||
|
//document.title = _('inactive_tab_title_raw')
|
||||||
|
|
||||||
|
rootScope.idle.deactivated = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
public checkInstance = () => {
|
||||||
|
if(this.deactivated) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const time = Date.now();
|
||||||
|
const idle = rootScope.idle && rootScope.idle.isIDLE;
|
||||||
|
const newInstance: AppInstance = {
|
||||||
|
id: this.instanceID,
|
||||||
|
idle,
|
||||||
|
time
|
||||||
|
};
|
||||||
|
|
||||||
|
sessionStorage.get('xt_instance').then((curInstance: AppInstance) => {
|
||||||
|
// console.log(dT(), 'check instance', newInstance, curInstance)
|
||||||
|
if(!idle ||
|
||||||
|
!curInstance ||
|
||||||
|
curInstance.id == this.instanceID ||
|
||||||
|
curInstance.time < time - 20000) {
|
||||||
|
sessionStorage.set({xt_instance: newInstance});
|
||||||
|
if(!this.masterInstance) {
|
||||||
|
//MtpNetworkerFactory.startAll();
|
||||||
|
if(!this.initial) {
|
||||||
|
this.initial = true;
|
||||||
|
} else {
|
||||||
|
this.log.warn('now master instance', newInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.masterInstance = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(this.deactivateTimeout) {
|
||||||
|
clearTimeout(this.deactivateTimeout);
|
||||||
|
this.deactivateTimeout = 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(this.masterInstance) {
|
||||||
|
//MtpNetworkerFactory.stopAll();
|
||||||
|
this.log.warn('now idle instance', newInstance);
|
||||||
|
if(!this.deactivateTimeout) {
|
||||||
|
this.deactivateTimeout = window.setTimeout(this.deactivateInstance, 30000);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.masterInstance = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const singleInstance = new SingleInstance();
|
||||||
|
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.singleInstance = singleInstance);
|
||||||
|
export default singleInstance;
|
@ -238,7 +238,7 @@ namespace RichTextProcessor {
|
|||||||
})
|
})
|
||||||
} */
|
} */
|
||||||
|
|
||||||
export function parseMarkdown(text: string, currentEntities: MessageEntity[], noTrim?: any): string {
|
export function parseMarkdown(text: string, currentEntities: MessageEntity[], noTrim?: boolean): string {
|
||||||
/* if(!markdownTestRegExp.test(text)) {
|
/* if(!markdownTestRegExp.test(text)) {
|
||||||
return noTrim ? text : text.trim();
|
return noTrim ? text : text.trim();
|
||||||
} */
|
} */
|
||||||
|
@ -122,6 +122,7 @@ export class RootScope extends EventListenerBase<{
|
|||||||
public myId = 0;
|
public myId = 0;
|
||||||
public idle = {
|
public idle = {
|
||||||
isIDLE: true,
|
isIDLE: true,
|
||||||
|
deactivated: false,
|
||||||
focusPromise: Promise.resolve(),
|
focusPromise: Promise.resolve(),
|
||||||
focusResolve: () => {}
|
focusResolve: () => {}
|
||||||
};
|
};
|
||||||
@ -158,20 +159,30 @@ export class RootScope extends EventListenerBase<{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public setThemeListener() {
|
public setThemeListener() {
|
||||||
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
try {
|
||||||
const checkDarkMode = () => {
|
const darkModeMediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
|
||||||
//const theme = this.getTheme();
|
const checkDarkMode = () => {
|
||||||
this.systemTheme = darkModeMediaQuery.matches ? 'night' : 'day';
|
//const theme = this.getTheme();
|
||||||
//const newTheme = this.getTheme();
|
this.systemTheme = darkModeMediaQuery.matches ? 'night' : 'day';
|
||||||
|
//const newTheme = this.getTheme();
|
||||||
|
|
||||||
if(this.myId) {
|
if(this.myId) {
|
||||||
this.broadcast('theme_change');
|
this.broadcast('theme_change');
|
||||||
} else {
|
} else {
|
||||||
this.setTheme();
|
this.setTheme();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if('addEventListener' in darkModeMediaQuery) {
|
||||||
|
darkModeMediaQuery.addEventListener('change', checkDarkMode);
|
||||||
|
} else if('addListener' in darkModeMediaQuery) {
|
||||||
|
(darkModeMediaQuery as any).addListener(checkDarkMode);
|
||||||
}
|
}
|
||||||
};
|
|
||||||
darkModeMediaQuery.addEventListener('change', checkDarkMode);
|
checkDarkMode();
|
||||||
checkDarkMode();
|
} catch(err) {
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public setTheme() {
|
public setTheme() {
|
||||||
|
@ -9,65 +9,26 @@
|
|||||||
* https://github.com/zhukov/webogram/blob/master/LICENSE
|
* https://github.com/zhukov/webogram/blob/master/LICENSE
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import Config from './config';
|
import cleanSearchText from '../helpers/cleanSearchText';
|
||||||
|
|
||||||
export type SearchIndex = {
|
export default class SearchIndex<SearchWhat> {
|
||||||
fullTexts: {
|
private fullTexts: Map<SearchWhat, string> = new Map();
|
||||||
[peerId: string]: string
|
|
||||||
}/* ,
|
|
||||||
shortIndexes: {
|
|
||||||
[shortStr: string]: number[]
|
|
||||||
} */
|
|
||||||
};
|
|
||||||
|
|
||||||
class SearchIndexManager {
|
public indexObject(id: SearchWhat, searchText: string) {
|
||||||
public static badCharsRe = /[`~!@#$%^&*()\-_=+\[\]\\|{}'";:\/?.>,<]+/g;
|
|
||||||
public static trimRe = /^\s+|\s$/g;
|
|
||||||
|
|
||||||
public createIndex(): SearchIndex {
|
|
||||||
return {
|
|
||||||
fullTexts: {}/* ,
|
|
||||||
shortIndexes: {} */
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public cleanSearchText(text: string, latinize = true) {
|
|
||||||
const hasTag = text.charAt(0) === '%';
|
|
||||||
text = text.replace(SearchIndexManager['badCharsRe'], '').replace(SearchIndexManager['trimRe'], '');
|
|
||||||
if(latinize) {
|
|
||||||
text = text.replace(/[^A-Za-z0-9]/g, (ch) => {
|
|
||||||
const latinizeCh = Config.LatinizeMap[ch];
|
|
||||||
return latinizeCh !== undefined ? latinizeCh : ch;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
text = text.toLowerCase();
|
|
||||||
if(hasTag) {
|
|
||||||
text = '%' + text;
|
|
||||||
}
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
public cleanUsername(username: string) {
|
|
||||||
return username && username.toLowerCase() || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
public indexObject(id: number, searchText: string, searchIndex: SearchIndex) {
|
|
||||||
/* if(searchIndex.fullTexts.hasOwnProperty(id)) {
|
/* if(searchIndex.fullTexts.hasOwnProperty(id)) {
|
||||||
return false;
|
return false;
|
||||||
} */
|
} */
|
||||||
|
|
||||||
if(searchText.trim()) {
|
if(searchText.trim()) {
|
||||||
searchText = this.cleanSearchText(searchText);
|
searchText = cleanSearchText(searchText);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!searchText) {
|
if(!searchText) {
|
||||||
delete searchIndex.fullTexts[id];
|
this.fullTexts.delete(id);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchIndex.fullTexts[id] = searchText;
|
this.fullTexts.set(id, searchText);
|
||||||
|
|
||||||
/* const shortIndexes = searchIndex.shortIndexes;
|
/* const shortIndexes = searchIndex.shortIndexes;
|
||||||
searchText.split(' ').forEach((searchWord) => {
|
searchText.split(' ').forEach((searchWord) => {
|
||||||
@ -84,17 +45,15 @@ class SearchIndexManager {
|
|||||||
}); */
|
}); */
|
||||||
}
|
}
|
||||||
|
|
||||||
public search(query: string, searchIndex: SearchIndex) {
|
public search(query: string) {
|
||||||
const fullTexts = searchIndex.fullTexts;
|
const fullTexts = this.fullTexts;
|
||||||
//const shortIndexes = searchIndex.shortIndexes;
|
//const shortIndexes = searchIndex.shortIndexes;
|
||||||
|
|
||||||
query = this.cleanSearchText(query);
|
query = cleanSearchText(query);
|
||||||
|
|
||||||
const newFoundObjs: {[peerId: string]: true} = {};
|
const newFoundObjs: Array<{fullText: string, what: SearchWhat}> = [];
|
||||||
const queryWords = query.split(' ');
|
const queryWords = query.split(' ');
|
||||||
for(const peerId in fullTexts) {
|
fullTexts.forEach((fullText, what) => {
|
||||||
const fullText = fullTexts[peerId];
|
|
||||||
|
|
||||||
let found = true;
|
let found = true;
|
||||||
for(const word of queryWords) { // * verify that all words are found
|
for(const word of queryWords) { // * verify that all words are found
|
||||||
const idx = fullText.indexOf(word);
|
const idx = fullText.indexOf(word);
|
||||||
@ -105,10 +64,12 @@ class SearchIndexManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(found) {
|
if(found) {
|
||||||
newFoundObjs[peerId] = true;
|
newFoundObjs.push({fullText, what});
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|
||||||
|
//newFoundObjs.sort((a, b) => a.fullText.localeCompare(b.fullText));
|
||||||
|
const newFoundObjs2: Set<SearchWhat> = new Set(newFoundObjs.map(o => o.what));
|
||||||
|
|
||||||
/* const queryWords = query.split(' ');
|
/* const queryWords = query.split(' ');
|
||||||
let foundArr: number[];
|
let foundArr: number[];
|
||||||
@ -139,8 +100,6 @@ class SearchIndexManager {
|
|||||||
}
|
}
|
||||||
} */
|
} */
|
||||||
|
|
||||||
return newFoundObjs;
|
return newFoundObjs2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default new SearchIndexManager();
|
|
@ -7,6 +7,7 @@
|
|||||||
import type { ChatSavedPosition } from './appManagers/appImManager';
|
import type { ChatSavedPosition } from './appManagers/appImManager';
|
||||||
import type { State } from './appManagers/appStateManager';
|
import type { State } from './appManagers/appStateManager';
|
||||||
import type { AppDraftsManager } from './appManagers/appDraftsManager';
|
import type { AppDraftsManager } from './appManagers/appDraftsManager';
|
||||||
|
import type { AppInstance } from './mtproto/singleInstance';
|
||||||
import { MOUNT_CLASS_TO } from '../config/debug';
|
import { MOUNT_CLASS_TO } from '../config/debug';
|
||||||
import { LangPackDifference } from '../layer';
|
import { LangPackDifference } from '../layer';
|
||||||
import AppStorage from './storage';
|
import AppStorage from './storage';
|
||||||
@ -21,6 +22,7 @@ const sessionStorage = new AppStorage<{
|
|||||||
dc5_auth_key: any,
|
dc5_auth_key: any,
|
||||||
max_seen_msg: number,
|
max_seen_msg: number,
|
||||||
server_time_offset: number,
|
server_time_offset: number,
|
||||||
|
xt_instance: AppInstance,
|
||||||
|
|
||||||
chatPositions: {
|
chatPositions: {
|
||||||
[peerId_threadId: string]: ChatSavedPosition
|
[peerId_threadId: string]: ChatSavedPosition
|
||||||
|
@ -20,7 +20,7 @@ import type { ApiUpdatesManager } from "../appManagers/apiUpdatesManager";
|
|||||||
import type { ServerTimeManager } from "../mtproto/serverTimeManager";
|
import type { ServerTimeManager } from "../mtproto/serverTimeManager";
|
||||||
import { tsNow } from "../../helpers/date";
|
import { tsNow } from "../../helpers/date";
|
||||||
import apiManager from "../mtproto/mtprotoworker";
|
import apiManager from "../mtproto/mtprotoworker";
|
||||||
import searchIndexManager from "../searchIndexManager";
|
import SearchIndex from "../searchIndex";
|
||||||
import { forEachReverse, insertInDescendSortedArray } from "../../helpers/array";
|
import { forEachReverse, insertInDescendSortedArray } from "../../helpers/array";
|
||||||
import rootScope from "../rootScope";
|
import rootScope from "../rootScope";
|
||||||
import { safeReplaceObject } from "../../helpers/object";
|
import { safeReplaceObject } from "../../helpers/object";
|
||||||
@ -38,7 +38,7 @@ export default class DialogsStorage {
|
|||||||
private pinnedOrders: {[folder_id: number]: number[]};
|
private pinnedOrders: {[folder_id: number]: number[]};
|
||||||
private dialogsNum: number;
|
private dialogsNum: number;
|
||||||
|
|
||||||
private dialogsIndex = searchIndexManager.createIndex();
|
private dialogsIndex = new SearchIndex<number>();
|
||||||
|
|
||||||
private cachedResults: {
|
private cachedResults: {
|
||||||
query: string,
|
query: string,
|
||||||
@ -71,7 +71,7 @@ export default class DialogsStorage {
|
|||||||
const dialog = this.getDialogOnly(peerId);
|
const dialog = this.getDialogOnly(peerId);
|
||||||
if(dialog) {
|
if(dialog) {
|
||||||
const peerText = appPeersManager.getPeerSearchText(peerId);
|
const peerText = appPeersManager.getPeerSearchText(peerId);
|
||||||
searchIndexManager.indexObject(peerId, peerText, this.dialogsIndex);
|
this.dialogsIndex.indexObject(peerId, peerText);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -340,7 +340,7 @@ export default class DialogsStorage {
|
|||||||
if(foundDialog[0]) {
|
if(foundDialog[0]) {
|
||||||
this.byFolders[foundDialog[0].folder_id].splice(foundDialog[1], 1);
|
this.byFolders[foundDialog[0].folder_id].splice(foundDialog[1], 1);
|
||||||
delete this.dialogs[peerId];
|
delete this.dialogs[peerId];
|
||||||
searchIndexManager.indexObject(peerId, '', this.dialogsIndex);
|
this.dialogsIndex.indexObject(peerId, '');
|
||||||
|
|
||||||
// clear from state
|
// clear from state
|
||||||
this.appStateManager.keepPeerSingle(0, 'topMessage_' + peerId);
|
this.appStateManager.keepPeerSingle(0, 'topMessage_' + peerId);
|
||||||
@ -435,7 +435,7 @@ export default class DialogsStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const peerText = this.appPeersManager.getPeerSearchText(peerId);
|
const peerText = this.appPeersManager.getPeerSearchText(peerId);
|
||||||
searchIndexManager.indexObject(peerId, peerText, this.dialogsIndex);
|
this.dialogsIndex.indexObject(peerId, peerText);
|
||||||
|
|
||||||
let mid: number, message;
|
let mid: number, message;
|
||||||
if(dialog.top_message) {
|
if(dialog.top_message) {
|
||||||
@ -537,13 +537,13 @@ export default class DialogsStorage {
|
|||||||
this.cachedResults.query = query;
|
this.cachedResults.query = query;
|
||||||
this.cachedResults.folderId = folderId;
|
this.cachedResults.folderId = folderId;
|
||||||
|
|
||||||
const results = searchIndexManager.search(query, this.dialogsIndex);
|
const results = this.dialogsIndex.search(query);
|
||||||
|
|
||||||
this.cachedResults.dialogs = [];
|
this.cachedResults.dialogs = [];
|
||||||
|
|
||||||
for(const peerId in this.dialogs) {
|
for(const peerId in this.dialogs) {
|
||||||
const dialog = this.dialogs[peerId];
|
const dialog = this.dialogs[peerId];
|
||||||
if(results[dialog.peerId] && dialog.folder_id === folderId) {
|
if(results.has(dialog.peerId) && dialog.folder_id === folderId) {
|
||||||
this.cachedResults.dialogs.push(dialog);
|
this.cachedResults.dialogs.push(dialog);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
19
src/scss/partials/_chatEmojiHelper.scss
Normal file
19
src/scss/partials/_chatEmojiHelper.scss
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
.emoji-helper {
|
||||||
|
height: 50px;
|
||||||
|
padding: .25rem !important;
|
||||||
|
|
||||||
|
> .scrollable {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.super-emojis {
|
||||||
|
display: block;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.super-emoji:not(.active) {
|
||||||
|
@include hover() {
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -163,65 +163,6 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
margin: 0 -.125rem;
|
margin: 0 -.125rem;
|
||||||
|
|
||||||
.category-items {
|
|
||||||
// ! No chrome 56 support
|
|
||||||
display: grid;
|
|
||||||
grid-column-gap: 2.44px;
|
|
||||||
grid-template-columns: repeat(auto-fill, 42px);
|
|
||||||
justify-content: space-between;
|
|
||||||
|
|
||||||
font-size: 2.25rem;
|
|
||||||
line-height: 2.25rem;
|
|
||||||
|
|
||||||
.category-item {
|
|
||||||
display: inline-block;
|
|
||||||
margin: 0 1.44px; // ! magic number
|
|
||||||
padding: 4px 4px;
|
|
||||||
line-height: inherit;
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
user-select: none;
|
|
||||||
|
|
||||||
width: 42px;
|
|
||||||
height: 42px;
|
|
||||||
|
|
||||||
html:not(.emoji-supported) & {
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji-placeholder {
|
|
||||||
position: absolute;
|
|
||||||
left: 7px;
|
|
||||||
top: 7px;
|
|
||||||
width: 1.75rem;
|
|
||||||
height: 1.75rem;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: var(--light-secondary-text-color);
|
|
||||||
|
|
||||||
@include animation-level(2) {
|
|
||||||
opacity: 0;
|
|
||||||
transition: opacity .2s ease-in-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@include animation-level(2) {
|
|
||||||
img {
|
|
||||||
opacity: 1;
|
|
||||||
transition: opacity .2s ease-in-out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.emoji {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
vertical-align: unset;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@include hover-background-effect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* &:first-child {
|
/* &:first-child {
|
||||||
//padding-top: 5px;
|
//padding-top: 5px;
|
||||||
} */
|
} */
|
||||||
|
@ -235,6 +235,7 @@ html.night {
|
|||||||
@import "partials/chatPinned";
|
@import "partials/chatPinned";
|
||||||
@import "partials/chatMarkupTooltip";
|
@import "partials/chatMarkupTooltip";
|
||||||
@import "partials/chatStickersHelper";
|
@import "partials/chatStickersHelper";
|
||||||
|
@import "partials/chatEmojiHelper";
|
||||||
@import "partials/chatSearch";
|
@import "partials/chatSearch";
|
||||||
@import "partials/chatDrop";
|
@import "partials/chatDrop";
|
||||||
@import "partials/crop";
|
@import "partials/crop";
|
||||||
@ -1213,3 +1214,62 @@ middle-ellipsis-element {
|
|||||||
border-radius: inherit;
|
border-radius: inherit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.super-emojis {
|
||||||
|
// ! No chrome 56 support
|
||||||
|
display: grid;
|
||||||
|
grid-column-gap: 2.44px;
|
||||||
|
grid-template-columns: repeat(auto-fill, 42px);
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
font-size: 2.25rem;
|
||||||
|
line-height: 2.25rem;
|
||||||
|
|
||||||
|
.super-emoji {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 0 1.44px; // ! magic number
|
||||||
|
padding: 4px 4px;
|
||||||
|
line-height: inherit;
|
||||||
|
border-radius: 8px;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
|
||||||
|
width: 42px;
|
||||||
|
height: 42px;
|
||||||
|
|
||||||
|
html:not(.emoji-supported) & {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji-placeholder {
|
||||||
|
position: absolute;
|
||||||
|
left: 7px;
|
||||||
|
top: 7px;
|
||||||
|
width: 1.75rem;
|
||||||
|
height: 1.75rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--light-secondary-text-color);
|
||||||
|
|
||||||
|
@include animation-level(2) {
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity .2s ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@include animation-level(2) {
|
||||||
|
img {
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity .2s ease-in-out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
vertical-align: unset;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include hover-background-effect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user