Browse Source

Emoji 13.0

Emoji autocomplete helper
Fix parsing gender emoji
Fix loading first comment
Fix selecting emoji in ESG
Deactivate parallel tabs
Fix bubble reply containers width
Reset pinned dialogs order
master
morethanwords 4 years ago
parent
commit
7dc7dde962
  1. 10
      src/components/appNavigationController.ts
  2. 66
      src/components/chat/autocompleteHelper.ts
  3. 2
      src/components/chat/contextMenu.ts
  4. 35
      src/components/chat/emojiHelper.ts
  5. 58
      src/components/chat/input.ts
  6. 39
      src/components/chat/stickersHelper.ts
  7. 23
      src/components/emoticonsDropdown/index.ts
  8. 83
      src/components/emoticonsDropdown/tabs/emoji.ts
  9. 4
      src/config/app.ts
  10. 36
      src/emoji_test.js
  11. 60
      src/helpers/blur.ts
  12. 23
      src/helpers/dom/attachListNavigation.ts
  13. 8
      src/helpers/dom/getRichElementValue.ts
  14. 4
      src/helpers/dom/getRichValueWithCaret.ts
  15. 46
      src/helpers/dom/setRichFocus.ts
  16. 6
      src/lang.ts
  17. 9
      src/layer.d.ts
  18. 23
      src/lib/appManagers/appDialogsManager.ts
  19. 58
      src/lib/appManagers/appEmojiManager.ts
  20. 36
      src/lib/appManagers/appImManager.ts
  21. 22
      src/lib/appManagers/appMessagesManager.ts
  22. 1
      src/lib/appManagers/appPhotosManager.ts
  23. 21
      src/lib/appManagers/appStateManager.ts
  24. 6
      src/lib/config.ts
  25. 8
      src/lib/mtproto/apiManager.ts
  26. 8
      src/lib/mtproto/mtproto.service.ts
  27. 7
      src/lib/mtproto/mtproto.worker.ts
  28. 11
      src/lib/mtproto/mtprotoworker.ts
  29. 63
      src/lib/mtproto/networker.ts
  30. 11
      src/lib/mtproto/networkerFactory.ts
  31. 56
      src/lib/mtproto/singleInstance.ts
  32. 11
      src/lib/mtproto/transports/tcpObfuscated.ts
  33. 21
      src/lib/richtextprocessor.ts
  34. 2
      src/lib/rootScope.ts
  35. 10
      src/lib/searchIndex.ts
  36. 4
      src/lib/storage.ts
  37. 4
      src/lib/storages/dialogs.ts
  38. 9
      src/scripts/format_jsons.js
  39. 20804
      src/scripts/in/emoji_pretty.json
  40. 7
      src/scripts/in/schema_additional_params.json
  41. 2
      src/scripts/out/countries.json
  42. 2
      src/scripts/out/emoji.json
  43. 5
      src/scss/partials/_chatBubble.scss
  44. 4
      src/scss/partials/_chatEmojiHelper.scss
  45. 6
      src/scss/partials/_chatlist.scss
  46. 22
      src/scss/partials/popups/_instanceDeactivated.scss
  47. 29
      src/scss/style.scss
  48. 3
      src/types.d.ts
  49. 19
      src/vendor/emoji/regex.ts

10
src/components/appNavigationController.ts

@ -12,10 +12,12 @@ import blurActiveElement from "../helpers/dom/blurActiveElement"; @@ -12,10 +12,12 @@ import blurActiveElement from "../helpers/dom/blurActiveElement";
import { cancelEvent } from "../helpers/dom/cancelEvent";
export type NavigationItem = {
type: 'left' | 'right' | 'im' | 'chat' | 'popup' | 'media' | 'menu' | 'esg' | 'multiselect' | 'input-helper' | 'markup' | 'global-search',
type: 'left' | 'right' | 'im' | 'chat' | 'popup' | 'media' | 'menu' |
'esg' | 'multiselect' | 'input-helper' | 'autocomplete-helper' | 'markup' | 'global-search',
onPop: (canAnimate: boolean) => boolean | void,
onEscape?: () => boolean,
noHistory?: boolean,
noBlurOnPop?: boolean,
};
export class AppNavigationController {
@ -61,9 +63,9 @@ export class AppNavigationController { @@ -61,9 +63,9 @@ export class AppNavigationController {
if(!item) return;
if(e.key === 'Escape' && (item.onEscape ? item.onEscape() : true)) {
cancelEvent(e);
this.back();
this.back(item.type);
}
}, {capture: true});
}, {capture: true, passive: false});
if(isMobileSafari) {
const options = {passive: true};
@ -117,7 +119,7 @@ export class AppNavigationController { @@ -117,7 +119,7 @@ export class AppNavigationController {
this.debug && this.log('popstate, navigation:', item, this.navigations);
if(good === false) {
this.pushItem(item);
} else {
} else if(!item.noBlurOnPop) {
blurActiveElement(); // no better place for it
}

66
src/components/chat/autocompleteHelper.ts

@ -4,29 +4,89 @@ @@ -4,29 +4,89 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import attachListNavigation from "../../helpers/dom/attachlistNavigation";
import EventListenerBase from "../../helpers/eventListenerBase";
import { isMobile } from "../../helpers/userAgent";
import rootScope from "../../lib/rootScope";
import appNavigationController, { NavigationItem } from "../appNavigationController";
import SetTransition from "../singleTransition";
export default class AutocompleteHelper extends EventListenerBase<{
hidden: () => void,
visible: () => void,
}> {
protected hidden = true;
protected container: HTMLElement;
protected list: HTMLElement;
protected resetTarget: () => void;
constructor(appendTo: HTMLElement) {
constructor(appendTo: HTMLElement,
private listType: 'xy' | 'x' | 'y',
private onSelect: (target: Element) => boolean | void,
private waitForKey?: string
) {
super(false);
this.container = document.createElement('div');
this.container.classList.add('autocomplete-helper', 'z-depth-1');
appendTo.append(this.container);
this.attachNavigation();
}
protected onVisible = () => {
const list = this.list;
const {detach, resetTarget} = attachListNavigation({
list,
type: this.listType,
onSelect: this.onSelect,
once: true,
waitForKey: this.waitForKey
});
this.resetTarget = resetTarget;
let navigationItem: NavigationItem;
if(!isMobile) {
navigationItem = {
type: 'autocomplete-helper',
onPop: () => this.toggle(true),
noBlurOnPop: true
};
appNavigationController.pushItem(navigationItem);
}
this.addEventListener('hidden', () => {
this.resetTarget = undefined;
list.innerHTML = '';
detach();
if(navigationItem) {
appNavigationController.removeItem(navigationItem);
}
}, true);
};
protected attachNavigation() {
this.addEventListener('visible', this.onVisible);
}
public toggle(hide?: boolean) {
hide = hide === undefined ? this.container.classList.contains('is-visible') : hide;
hide = hide === undefined ? this.container.classList.contains('is-visible') && !this.container.classList.contains('backwards') : hide;
if(this.hidden === hide) {
if(!hide && this.resetTarget) {
this.resetTarget();
}
return;
}
this.hidden = hide;
!this.hidden && this.dispatchEvent('visible'); // fire it before so target will be set
SetTransition(this.container, 'is-visible', !hide, rootScope.settings.animationsEnabled ? 200 : 0, () => {
this.dispatchEvent(hide ? 'hidden' : 'visible');
this.hidden && this.dispatchEvent('hidden');
});
}
}

2
src/components/chat/contextMenu.ts

@ -340,7 +340,7 @@ export default class ChatContextMenu { @@ -340,7 +340,7 @@ export default class ChatContextMenu {
private onCopyClick = () => {
if(isSelectionEmpty()) {
const mids = this.chat.selection.isSelecting ? [...this.chat.selection.selectedMids] : [this.mid];
const mids = this.chat.selection.isSelecting ? [...this.chat.selection.selectedMids].sort((a, b) => a - b) : [this.mid];
const str = mids.reduce((acc, mid) => {
const message = this.chat.getMessage(mid);
return acc + (message?.message ? message.message + '\n' : '');

35
src/components/chat/emojiHelper.ts

@ -1,41 +1,24 @@ @@ -1,41 +1,24 @@
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) => {
super(appendTo, 'x', (target) => {
this.chatInput.onEmojiSelected(getEmojiFromElement(target as any), true);
},
once: true
});
this.addEventListener('hidden', () => {
list.innerHTML = '';
detach();
}, true);
});
this.container.classList.add('emoji-helper');
}
private init() {
this.emojisContainer = document.createElement('div');
this.emojisContainer.classList.add('emoji-helper-emojis', 'super-emojis');
this.list = document.createElement('div');
this.list.classList.add('emoji-helper-emojis', 'super-emojis');
this.container.append(this.emojisContainer);
this.container.append(this.list);
this.scrollable = new ScrollableX(this.container);
}
@ -47,12 +30,16 @@ export default class EmojiHelper extends AutocompleteHelper { @@ -47,12 +30,16 @@ export default class EmojiHelper extends AutocompleteHelper {
}
if(emojis.length) {
this.emojisContainer.innerHTML = '';
this.list.innerHTML = '';
emojis.forEach(emoji => {
appendEmoji(emoji, this.emojisContainer);
appendEmoji(emoji, this.list, false, true);
});
}
if(!this.hidden) {
this.scrollable.container.scrollLeft = 0;
}
this.toggle(!emojis.length);
}
}

58
src/components/chat/input.ts

@ -58,8 +58,9 @@ import isSendShortcutPressed from '../../helpers/dom/isSendShortcutPressed'; @@ -58,8 +58,9 @@ import isSendShortcutPressed from '../../helpers/dom/isSendShortcutPressed';
import placeCaretAtEnd from '../../helpers/dom/placeCaretAtEnd';
import { MarkdownType, markdownTags } from '../../helpers/dom/getRichElementValue';
import getRichValueWithCaret from '../../helpers/dom/getRichValueWithCaret';
import cleanSearchText from '../../helpers/cleanSearchText';
import EmojiHelper from './emojiHelper';
import setRichFocus from '../../helpers/dom/setRichFocus';
import { toCodePoints } from '../../vendor/emoji';
const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@ -974,6 +975,7 @@ export default class ChatInput { @@ -974,6 +975,7 @@ export default class ChatInput {
}
private handleMarkdownShortcut = (e: KeyboardEvent) => {
// console.log('handleMarkdownShortcut', e);
const formatKeys: {[key: string]: MarkdownType} = {
'B': 'bold',
'I': 'italic',
@ -1145,14 +1147,55 @@ export default class ChatInput { @@ -1145,14 +1147,55 @@ export default class ChatInput {
public onEmojiSelected = (emoji: string, autocomplete: boolean) => {
if(autocomplete) {
const {value: fullValue, caretPos} = getRichValueWithCaret(this.messageInput);
const {value: fullValue, caretPos, entities} = getRichValueWithCaret(this.messageInput);
const pos = caretPos >= 0 ? caretPos : fullValue.length;
const suffix = fullValue.substr(pos);
const prefix = fullValue.substr(0, pos);
const suffix = fullValue.substr(pos);
const matches = prefix.match(ChatInput.AUTO_COMPLETE_REG_EXP);
console.log(matches);
const idx = matches.index + matches[1].length;
const matchIndex = matches.index + (matches[0].length - matches[2].length);
const newPrefix = prefix.slice(0, matchIndex);
const newValue = newPrefix + emoji + suffix;
// merge emojis
const hadEntities = RichTextProcessor.parseEntities(fullValue);
RichTextProcessor.mergeEntities(entities, hadEntities);
const emojiEntity: MessageEntity.messageEntityEmoji = {
_: 'messageEntityEmoji',
offset: 0,
length: emoji.length,
unicode: toCodePoints(emoji).join('-')
};
const addEntities: MessageEntity[] = [emojiEntity];
emojiEntity.offset = matchIndex;
addEntities.push({
_: 'messageEntityCaret',
length: 0,
offset: emojiEntity.offset + emojiEntity.length
});
// add offset to entities next to emoji
const diff = emojiEntity.length - matches[2].length;
entities.forEach(entity => {
if(entity.offset >= emojiEntity.offset) {
entity.offset += diff;
}
});
RichTextProcessor.mergeEntities(entities, addEntities);
//const saveExecuted = this.prepareDocumentExecute();
this.messageInputField.value = RichTextProcessor.wrapDraftText(newValue, {entities});
const caret = this.messageInput.querySelector('.composer-sel');
setRichFocus(this.messageInput, caret);
caret.remove();
//saveExecuted();
//document.execCommand('insertHTML', true, RichTextProcessor.wrapEmojiText(emoji));
//const str =
@ -1176,7 +1219,7 @@ export default class ChatInput { @@ -1176,7 +1219,7 @@ export default class ChatInput {
};
private checkAutocomplete(value?: string, caretPos?: number) {
return;
//return;
if(value === undefined) {
const r = getRichValueWithCaret(this.messageInputField.input, false);
@ -1260,7 +1303,8 @@ export default class ChatInput { @@ -1260,7 +1303,8 @@ export default class ChatInput {
}
this.appEmojiManager.getBothEmojiKeywords().then(() => {
const emojis = this.appEmojiManager.searchEmojis(matches[2]);
const q = matches[2].replace(/^:/, '');
const emojis = this.appEmojiManager.searchEmojis(q);
this.emojiHelper.renderEmojis(emojis);
//console.log(emojis);
});

39
src/components/chat/stickersHelper.ts

@ -4,7 +4,6 @@ @@ -4,7 +4,6 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import attachListNavigation from "../../helpers/dom/attachlistNavigation";
import { MyDocument } from "../../lib/appManagers/appDocsManager";
import { CHAT_ANIMATION_GROUP } from "../../lib/appManagers/appImManager";
import appStickersManager from "../../lib/appManagers/appStickersManager";
@ -15,33 +14,17 @@ import Scrollable from "../scrollable"; @@ -15,33 +14,17 @@ import Scrollable from "../scrollable";
import AutocompleteHelper from "./autocompleteHelper";
export default class StickersHelper extends AutocompleteHelper {
private stickersContainer: HTMLElement;
private scrollable: Scrollable;
private superStickerRenderer: SuperStickerRenderer;
private lazyLoadQueue: LazyLoadQueue;
private lastEmoticon = '';
constructor(appendTo: HTMLElement) {
super(appendTo);
this.container.classList.add('stickers-helper');
this.addEventListener('visible', () => {
const list = this.stickersContainer;
const {detach} = attachListNavigation({
list,
type: 'xy',
onSelect: (target) => {
super(appendTo, 'xy', (target) => {
EmoticonsDropdown.onMediaClick({target}, true);
},
once: true
});
}, 'ArrowUp');
this.addEventListener('hidden', () => {
list.innerHTML = '';
detach();
}, true);
});
this.container.classList.add('stickers-helper');
}
public checkEmoticon(emoticon: string) {
@ -73,7 +56,7 @@ export default class StickersHelper extends AutocompleteHelper { @@ -73,7 +56,7 @@ export default class StickersHelper extends AutocompleteHelper {
this.init = null;
}
const container = this.stickersContainer.cloneNode() as HTMLElement;
const container = this.list.cloneNode() as HTMLElement;
let ready: Promise<void>;
@ -92,8 +75,12 @@ export default class StickersHelper extends AutocompleteHelper { @@ -92,8 +75,12 @@ export default class StickersHelper extends AutocompleteHelper {
}
ready.then(() => {
this.stickersContainer.replaceWith(container);
this.stickersContainer = container;
if(!this.hidden) {
this.scrollable.container.scrollTop = 0;
}
this.list.replaceWith(container);
this.list = container;
this.toggle(!stickers.length);
this.scrollable.scrollTop = 0;
@ -102,10 +89,10 @@ export default class StickersHelper extends AutocompleteHelper { @@ -102,10 +89,10 @@ export default class StickersHelper extends AutocompleteHelper {
}
private init() {
this.stickersContainer = document.createElement('div');
this.stickersContainer.classList.add('stickers-helper-stickers', 'super-stickers');
this.list = document.createElement('div');
this.list.classList.add('stickers-helper-stickers', 'super-stickers');
this.container.append(this.stickersContainer);
this.container.append(this.list);
this.scrollable = new Scrollable(this.container);
this.lazyLoadQueue = new LazyLoadQueue();

23
src/components/emoticonsDropdown/index.ts

@ -43,9 +43,9 @@ export class EmoticonsDropdown { @@ -43,9 +43,9 @@ export class EmoticonsDropdown {
public static lazyLoadQueue = new LazyLoadQueue();
private element: HTMLElement;
public emojiTab: EmojiTab;
private emojiTab: EmojiTab;
public stickersTab: StickersTab;
public gifsTab: GifsTab;
private gifsTab: GifsTab;
private container: HTMLElement;
private tabsEl: HTMLElement;
@ -53,8 +53,8 @@ export class EmoticonsDropdown { @@ -53,8 +53,8 @@ export class EmoticonsDropdown {
private tabs: {[id: number]: EmoticonsTab};
public searchButton: HTMLElement;
public deleteBtn: HTMLElement;
private searchButton: HTMLElement;
private deleteBtn: HTMLElement;
private displayTimeout: number;
@ -73,6 +73,8 @@ export class EmoticonsDropdown { @@ -73,6 +73,8 @@ export class EmoticonsDropdown {
private selectTab: ReturnType<typeof horizontalMenu>;
private forceClose = false;
private savedRange: Range;
constructor() {
this.element = document.getElementById('emoji-dropdown') as HTMLDivElement;
}
@ -206,7 +208,7 @@ export class EmoticonsDropdown { @@ -206,7 +208,7 @@ export class EmoticonsDropdown {
this.deleteBtn.classList.toggle('hide', this.tabId !== 0);
};
public checkRights = () => {
private checkRights = () => {
const peerId = appImManager.chat.peerId;
const children = this.tabsEl.children;
const tabsElements = Array.from(children) as HTMLElement[];
@ -251,6 +253,11 @@ export class EmoticonsDropdown { @@ -251,6 +253,11 @@ export class EmoticonsDropdown {
if((this.element.style.display && enable === undefined) || enable) {
this.events.onOpen.forEach(cb => cb());
const sel = document.getSelection();
if(!sel.isCollapsed) {
this.savedRange = sel.getRangeAt(0);
}
EmoticonsDropdown.lazyLoadQueue.lock();
//EmoticonsDropdown.lazyLoadQueue.unlock();
animationIntersector.lockIntersectionGroup(EMOTICONSSTICKERGROUP);
@ -306,6 +313,8 @@ export class EmoticonsDropdown { @@ -306,6 +313,8 @@ export class EmoticonsDropdown {
this.container.classList.remove('disable-hover');
this.events.onCloseAfter.forEach(cb => cb());
this.savedRange = undefined;
}, isTouchSupported ? 0 : ANIMATION_DURATION);
/* if(isTouchSupported) {
@ -435,6 +444,10 @@ export class EmoticonsDropdown { @@ -435,6 +444,10 @@ export class EmoticonsDropdown {
lazyLoadQueue.unlockAndRefresh();
});
}
public getSavedRange() {
return this.savedRange;
}
}
const emoticonsDropdown = new EmoticonsDropdown();

83
src/components/emoticonsDropdown/tabs/emoji.ts

@ -4,21 +4,22 @@ @@ -4,21 +4,22 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import { EmoticonsDropdown, EmoticonsTab } from "..";
import emoticonsDropdown, { EmoticonsDropdown, EmoticonsTab } from "..";
import findUpClassName from "../../../helpers/dom/findUpClassName";
import { fastRaf } from "../../../helpers/schedulers";
import { fastRaf, pause } from "../../../helpers/schedulers";
import appEmojiManager from "../../../lib/appManagers/appEmojiManager";
import appImManager from "../../../lib/appManagers/appImManager";
import appStateManager from "../../../lib/appManagers/appStateManager";
import Config from "../../../lib/config";
import { i18n, LangPackKey } from "../../../lib/langPack";
import { RichTextProcessor } from "../../../lib/richtextprocessor";
import rootScope from "../../../lib/rootScope";
import { toCodePoints } from "../../../vendor/emoji";
import { putPreloader } from "../../misc";
import Scrollable from "../../scrollable";
import StickyIntersector from "../../stickyIntersector";
const loadedURLs: Set<string> = new Set();
export function appendEmoji(emoji: string, container: HTMLElement, prepend = false/* , unified = false */) {
export function appendEmoji(emoji: string, container: HTMLElement, prepend = false, unify = false) {
//const emoji = details.unified;
//const emoji = (details.unified as string).split('-')
//.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
@ -27,18 +28,18 @@ export function appendEmoji(emoji: string, container: HTMLElement, prepend = fal @@ -27,18 +28,18 @@ export function appendEmoji(emoji: string, container: HTMLElement, prepend = fal
spanEmoji.classList.add('super-emoji');
let kek: string;
/* if(unified) {
kek = RichTextProcessor.wrapRichText('_', {
if(unify) {
kek = RichTextProcessor.wrapRichText(emoji, {
entities: [{
_: 'messageEntityEmoji',
offset: 0,
length: emoji.split('-').length,
unicode: emoji
length: emoji.length,
unicode: toCodePoints(emoji).join('-')
}]
});
} else { */
} else {
kek = RichTextProcessor.wrapEmojiText(emoji);
//}
}
/* if(!kek.includes('emoji')) {
console.log(emoji, kek, spanEmoji, emoji.length, new TextEncoder().encode(emoji), emojiUnicode(emoji));
@ -104,7 +105,6 @@ export function getEmojiFromElement(element: HTMLElement) { @@ -104,7 +105,6 @@ export function getEmojiFromElement(element: HTMLElement) {
export default class EmojiTab implements EmoticonsTab {
private content: HTMLElement;
private recent: string[] = [];
private recentItemsDiv: HTMLElement;
private scroll: Scrollable;
@ -170,12 +170,27 @@ export default class EmojiTab implements EmoticonsTab { @@ -170,12 +170,27 @@ export default class EmojiTab implements EmoticonsTab {
div.append(titleDiv, itemsDiv);
emojis.forEach(emoji => {
emojis.forEach(unified => {
/* if(emojiUnicode(emoji) === '1f481-200d-2642') {
console.log('append emoji', emoji, emojiUnicode(emoji));
} */
emoji = emoji.split('-').reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
let emoji = unified.split('-').reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
//if(emoji.includes('🕵')) {
//console.log('toCodePoints', toCodePoints(emoji));
//emoji = emoji.replace(/(\u200d[\u2640\u2642\u2695])(?!\ufe0f)/, '\ufe0f$1');
// const zwjIndex = emoji.indexOf('\u200d');
// if(zwjIndex !== -1 && !emoji.includes('\ufe0f')) {
// /* if(zwjIndex !== (emoji.length - 1)) {
// emoji = emoji.replace(/(\u200d)/g, '\ufe0f$1');
// } */
// emoji += '\ufe0f';
// //emoji += '\ufe0f';
// }
//debugger;
//}
appendEmoji(emoji/* .replace(/[\ufe0f\u2640\u2642\u2695]/g, '') */, itemsDiv, false/* , false */);
@ -197,22 +212,17 @@ export default class EmojiTab implements EmoticonsTab { @@ -197,22 +212,17 @@ export default class EmojiTab implements EmoticonsTab {
const preloader = putPreloader(this.content, true);
Promise.all([
new Promise((resolve) => setTimeout(resolve, 200)),
appStateManager.getState().then(state => {
if(Array.isArray(state.recentEmoji)) {
this.recent = state.recentEmoji;
}
})
]).then(() => {
pause(200),
appEmojiManager.getRecentEmojis()
]).then(([_, recent]) => {
preloader.remove();
this.recentItemsDiv = divs['Emoji.Recent'].querySelector('.super-emojis');
for(const emoji of this.recent) {
for(const emoji of recent) {
appendEmoji(emoji, this.recentItemsDiv);
}
this.recentItemsDiv.parentElement.classList.toggle('hide', !this.recent.length);
this.recentItemsDiv.parentElement.classList.toggle('hide', !this.recentItemsDiv.childElementCount);
categories.unshift('Emoji.Recent');
categories.map(category => {
@ -246,11 +256,21 @@ export default class EmojiTab implements EmoticonsTab { @@ -246,11 +256,21 @@ export default class EmojiTab implements EmoticonsTab {
target = target.firstChild as HTMLElement;
} else if(target.tagName === 'DIV') return;
//console.log('contentEmoji div', target);
appImManager.chat.input.messageInput.innerHTML += RichTextProcessor.emojiSupported ?
// set selection range
const savedRange = emoticonsDropdown.getSavedRange();
if(savedRange) {
const sel = document.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
}
const html = RichTextProcessor.emojiSupported ?
(target.nodeType === 3 ? target.nodeValue : target.innerHTML) :
target.outerHTML;
// insert emoji in input
document.execCommand('insertHTML', true, html);
// Recent
const emoji = getEmojiFromElement(target);
(Array.from(this.recentItemsDiv.children) as HTMLElement[]).forEach((el, idx) => {
@ -259,18 +279,11 @@ export default class EmojiTab implements EmoticonsTab { @@ -259,18 +279,11 @@ export default class EmojiTab implements EmoticonsTab {
el.remove();
}
});
//const scrollHeight = this.recentItemsDiv.scrollHeight;
appendEmoji(emoji, this.recentItemsDiv, true);
this.recent.findAndSplice(e => e === emoji);
this.recent.unshift(emoji);
if(this.recent.length > 36) {
this.recent.length = 36;
}
this.recentItemsDiv.parentElement.classList.toggle('hide', !this.recent.length);
appendEmoji(emoji, this.recentItemsDiv, true);
appStateManager.pushToState('recentEmoji', this.recent);
appEmojiManager.pushRecentEmoji(emoji);
this.recentItemsDiv.parentElement.classList.remove('hide');
// Append to input
const event = new Event('input', {bubbles: true, cancelable: true});

4
src/config/app.ts

@ -12,8 +12,8 @@ @@ -12,8 +12,8 @@
const App = {
id: 1025907,
hash: '452b0359b988148995f22ff0f4229750',
version: '0.5.3',
langPackVersion: '0.1.8',
version: '0.5.4',
langPackVersion: '0.1.9',
langPack: 'macos',
langPackCode: 'en',
domains: [] as string[],

36
src/emoji_test.js

@ -0,0 +1,36 @@ @@ -0,0 +1,36 @@
function toCodePoints(unicodeSurrogates) {
const points = [];
let char = 0;
let previous = 0;
let i = 0;
while(i < unicodeSurrogates.length) {
char = unicodeSurrogates.charCodeAt(i++);
if(previous) {
points.push((0x10000 + ((previous - 0xd800) << 10) + (char - 0xdc00)).toString(16));
previous = 0;
} else if (char > 0xd800 && char <= 0xdbff) {
previous = char;
} else {
points.push(char.toString(16));
}
}
if(points.length && points[0].length === 2) {
points[0] = '00' + points[0];
}
return points;
}
var eye = "👁🗨";
toCodePoints(eye)
function ccc(str) {
return str.split('').map(c => c.charCodeAt(0).toString(16));
}
ccc("🏳🌈")
ccc("🏋");
"🏌♀";
var regexp = new RegExp("(?:👨🏻🤝👨<EFBFBD>[<EFBFBD>-<EFBFBD>]|👨🏼🤝👨<EFBFBD>[<EFBFBD><EFBFBD>-<EFBFBD>]|👨🏽🤝👨<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|👨🏾🤝👨<EFBFBD>[<EFBFBD>-<EFBFBD><EFBFBD>]|👨🏿🤝👨<EFBFBD>[<EFBFBD>-<EFBFBD>]|👩🏻🤝👨<EFBFBD>[<EFBFBD>-<EFBFBD>]|👩🏻🤝👩<EFBFBD>[<EFBFBD>-<EFBFBD>]|👩🏼🤝👨<EFBFBD>[<EFBFBD><EFBFBD>-<EFBFBD>]|👩🏼🤝👩<EFBFBD>[<EFBFBD><EFBFBD>-<EFBFBD>]|👩🏽🤝👨<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|👩🏽🤝👩<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|👩🏾🤝👨<EFBFBD>[<EFBFBD>-<EFBFBD><EFBFBD>]|👩🏾🤝👩<EFBFBD>[<EFBFBD>-<EFBFBD><EFBFBD>]|👩🏿🤝👨<EFBFBD>[<EFBFBD>-<EFBFBD>]|👩🏿🤝👩<EFBFBD>[<EFBFBD>-<EFBFBD>]|🧑🏻🤝🧑<EFBFBD>[<EFBFBD>-<EFBFBD>]|🧑🏼🤝🧑<EFBFBD>[<EFBFBD>-<EFBFBD>]|🧑🏽🤝🧑<EFBFBD>[<EFBFBD>-<EFBFBD>]|🧑🏾🤝🧑<EFBFBD>[<EFBFBD>-<EFBFBD>]|🧑🏿🤝🧑<EFBFBD>[<EFBFBD>-<EFBFBD>]|🧑🤝🧑|👫<EFBFBD>[<EFBFBD>-<EFBFBD>]|👬<EFBFBD>[<EFBFBD>-<EFBFBD>]|👭<EFBFBD>[<EFBFBD>-<EFBFBD>]|<EFBFBD>[<EFBFBD>-<EFBFBD>])|(?:<EFBFBD>[<EFBFBD><EFBFBD>]|🧑)(?:<EFBFBD>[<EFBFBD>-<EFBFBD>])?(?:⚕|⚖|✈|<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|<EFBFBD>[<EFBFBD>-<EFBFBD><EFBFBD><EFBFBD>])|(?:<EFBFBD>[<EFBFBD><EFBFBD>]|<EFBFBD>[<EFBFBD><EFBFBD>]|⛹)((?:<EFBFBD>[<EFBFBD>-<EFBFBD>])[♀♂])|(?:<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD>]|<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD>]|<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD>])(?:<EFBFBD>[<EFBFBD>-<EFBFBD>])?[♀♂]|(?:👨💋👨|👨👨👦👦|👨👨👧<EFBFBD>[<EFBFBD><EFBFBD>]|👨👩👦👦|👨👩👧<EFBFBD>[<EFBFBD><EFBFBD>]|👩💋<EFBFBD>[<EFBFBD><EFBFBD>]|👩👩👦👦|👩👩👧<EFBFBD>[<EFBFBD><EFBFBD>]|👨👨|👨👦👦|👨👧<EFBFBD>[<EFBFBD><EFBFBD>]|👨👨<EFBFBD>[<EFBFBD><EFBFBD>]|👨👩<EFBFBD>[<EFBFBD><EFBFBD>]|👩<EFBFBD>[<EFBFBD><EFBFBD>]|👩👦👦|👩👧<EFBFBD>[<EFBFBD><EFBFBD>]|👩👩<EFBFBD>[<EFBFBD><EFBFBD>]|🏳⚧|🏳🌈|🏴☠|🐕🦺|🐻❄|👁🗨|👨<EFBFBD>[<EFBFBD><EFBFBD>]|👩<EFBFBD>[<EFBFBD><EFBFBD>]|👯♀|👯♂|🤼♀|🤼♂|🧞♀|🧞♂|🧟♀|🧟♂|🐈⬛)|[#*0-9]?|(?:[©®™♟])|(?:<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|[‼⁉ℹ↔-↙↩↪⌚⌛⌨⏏⏭-⏯⏱⏲⏸-⏺Ⓜ▪▫▶◀◻-◾☀-☄☎☑☔☕☘☠☢☣☦☪☮☯☸-☺♀♂♈-♓♠♣♥♦♨♻♿⚒-⚗⚙⚛⚜⚠⚡⚧⚪⚫⚰⚱⚽⚾⛄⛅⛈⛏⛑⛓⛔⛩⛪⛰-⛵⛸⛺⛽✂✈✉✏✒✔✖✝✡✳✴❄❇❗❣❤➡⤴⤵⬅-⬇⬛⬜⭐⭕〰〽㊗㊙])(?:(?!))|(?:(?:<EFBFBD>[<EFBFBD><EFBFBD>]|<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD>]|[☝⛷⛹✌✍])(?:(?!))|(?:<EFBFBD>[<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD>]|<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD>]|<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD>]|[✊✋]))(?:<EFBFBD>[<EFBFBD>-<EFBFBD>])?|(?:🏴󠁧󠁢󠁥󠁮󠁧󠁿|🏴󠁧󠁢󠁳󠁣󠁴󠁿|🏴󠁧󠁢󠁷󠁬󠁳󠁿|🇦<EFBFBD>[<EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|🇧<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|🇨<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD>]|🇩<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|🇪<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD>]|🇫<EFBFBD>[<EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|🇬<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD>]|🇭<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|🇮<EFBFBD>[<EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD>]|🇯<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|🇰<EFBFBD>[<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|🇱<EFBFBD>[<EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>]|🇲<EFBFBD>[<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD>]|🇳<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|🇴🇲|🇵<EFBFBD>[<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD>]|🇶🇦|🇷<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|🇸<EFBFBD>[<EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD>]|🇹<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|🇺<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|🇻<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>]|🇼<EFBFBD>[<EFBFBD><EFBFBD>]|🇽🇰|🇾<EFBFBD>[<EFBFBD><EFBFBD>]|🇿<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD>]|<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD>]|<EFBFBD>[<EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD>]|<EFBFBD>[<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD><EFBFBD>-<EFBFBD>]|[⏩-⏬⏰⏳♾⛎✅✨❌❎❓-❕➕-➗➰➿])|")

60
src/helpers/blur.ts

@ -4,39 +4,42 @@ @@ -4,39 +4,42 @@
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import _DEBUG from '../config/debug';
import fastBlur from '../vendor/fastBlur';
import type fastBlur from '../vendor/fastBlur';
import addHeavyTask from './heavyQueue';
const RADIUS = 2;
const ITERATIONS = 2;
const DEBUG = _DEBUG && true;
function processBlur(dataUri: string, radius: number, iterations: number) {
return new Promise<string>((resolve) => {
const img = new Image();
const perf = performance.now();
if(DEBUG) {
console.log('[blur] start');
const isFilterAvailable = 'filter' in (document.createElement('canvas').getContext('2d') || {});
let requireBlurPromise: Promise<any>;
let fastBlurFunc: typeof fastBlur;
if(!isFilterAvailable) {
requireBlurPromise = import('../vendor/fastBlur').then(m => {
fastBlurFunc = m.default;
});
} else {
requireBlurPromise = Promise.resolve();
}
img.onload = () => {
function processBlurNext(img: HTMLImageElement, radius: number, iterations: number) {
return new Promise<string>((resolve) => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d')!;
//ctx.filter = 'blur(2px)';
const ctx = canvas.getContext('2d', {alpha: false});
if(isFilterAvailable) {
ctx.filter = `blur(${radius}px)`;
ctx.drawImage(img, -radius * 2, -radius * 2, canvas.width + radius * 4, canvas.height + radius * 4);
} else {
ctx.drawImage(img, 0, 0);
fastBlur(ctx, 0, 0, canvas.width, canvas.height, radius, iterations);
fastBlurFunc(ctx, 0, 0, canvas.width, canvas.height, radius, iterations);
}
resolve(canvas.toDataURL());
if(DEBUG) {
/* if(DEBUG) {
console.log(`[blur] end, radius: ${radius}, iterations: ${iterations}, time: ${performance.now() - perf}`);
}
} */
/* canvas.toBlob(blob => {
resolve(URL.createObjectURL(blob));
@ -45,9 +48,6 @@ function processBlur(dataUri: string, radius: number, iterations: number) { @@ -45,9 +48,6 @@ function processBlur(dataUri: string, radius: number, iterations: number) {
console.log(`[blur] end, radius: ${radius}, iterations: ${iterations}, time: ${performance.now() - perf}`);
}
}); */
};
img.src = dataUri;
});
}
@ -67,12 +67,30 @@ export default function blur(dataUri: string, radius: number = RADIUS, iteration @@ -67,12 +67,30 @@ export default function blur(dataUri: string, radius: number = RADIUS, iteration
if(blurPromises.has(dataUri)) return blurPromises.get(dataUri);
const promise = new Promise<string>((resolve) => {
//return resolve(dataUri);
requireBlurPromise.then(() => {
const img = new Image();
img.onload = () => {
if(isFilterAvailable) {
processBlurNext(img, radius, iterations).then(resolve);
} else {
addHeavyTask({
items: [[img, radius, iterations]],
context: null,
process: processBlurNext
}, 'unshift').then(results => {
resolve(results[0]);
});
}
};
img.src = dataUri;
/* addHeavyTask({
items: [[dataUri, radius, iterations]],
context: null,
process: processBlur
}, 'unshift').then(results => {
resolve(results[0]);
}); */
});
});

23
src/helpers/dom/attachListNavigation.ts

@ -14,11 +14,12 @@ type ArrowKey = 'ArrowUp' | 'ArrowDown' | 'ArrowLeft' | 'ArrowRight'; @@ -14,11 +14,12 @@ type ArrowKey = 'ArrowUp' | 'ArrowDown' | 'ArrowLeft' | 'ArrowRight';
const HANDLE_EVENT = 'keydown';
const ACTIVE_CLASS_NAME = 'active';
export default function attachListNavigation({list, type, onSelect, once}: {
export default function attachListNavigation({list, type, onSelect, once, waitForKey}: {
list: HTMLElement,
type: 'xy' | 'x' | 'y',
onSelect: (target: Element) => void | boolean,
once: boolean,
waitForKey?: string
}) {
const keyNames: Set<ArrowKey> = new Set(['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight']);
@ -82,7 +83,7 @@ export default function attachListNavigation({list, type, onSelect, once}: { @@ -82,7 +83,7 @@ export default function attachListNavigation({list, type, onSelect, once}: {
handleArrowKey = (currentTarget, key) => getNextTargetX(currentTarget, key === 'ArrowRight' || key === 'ArrowDown');
}
const onKeyDown = (e: KeyboardEvent) => {
let onKeyDown = (e: KeyboardEvent) => {
if(!keyNames.has(e.key as any)) {
if(e.key === 'Enter') {
cancelEvent(e);
@ -99,8 +100,6 @@ export default function attachListNavigation({list, type, onSelect, once}: { @@ -99,8 +100,6 @@ export default function attachListNavigation({list, type, onSelect, once}: {
currentTarget = handleArrowKey(currentTarget, e.key as any);
setCurrentTarget(currentTarget, true);
}
return false;
};
const scrollable = findUpClassName(list, 'scrollable');
@ -142,10 +141,26 @@ export default function attachListNavigation({list, type, onSelect, once}: { @@ -142,10 +141,26 @@ export default function attachListNavigation({list, type, onSelect, once}: {
};
const resetTarget = () => {
if(waitForKey) return;
setCurrentTarget(list.firstElementChild, false);
};
if(waitForKey) {
const _onKeyDown = onKeyDown;
onKeyDown = (e) => {
if(e.key === waitForKey) {
cancelEvent(e);
document.removeEventListener(HANDLE_EVENT, onKeyDown, {capture: true});
onKeyDown = _onKeyDown;
document.addEventListener(HANDLE_EVENT, onKeyDown, {capture: true, passive: false});
resetTarget();
}
};
} else {
resetTarget();
}
// const input = document.activeElement as HTMLElement;
// input.addEventListener(HANDLE_EVENT, onKeyDown, {capture: true, passive: false});

8
src/helpers/dom/getRichElementValue.ts

@ -45,12 +45,13 @@ export const markdownTags: {[type in MarkdownType]: MarkdownTag} = { @@ -45,12 +45,13 @@ export const markdownTags: {[type in MarkdownType]: MarkdownTag} = {
export default function getRichElementValue(node: HTMLElement, lines: string[], line: string[], selNode?: Node, selOffset?: number, entities?: MessageEntity[], offset = {offset: 0}) {
if(node.nodeType === 3) { // TEXT
const nodeValue = node.nodeValue;
if(selNode === node) {
const value = node.nodeValue;
line.push(value.substr(0, selOffset) + '\x01' + value.substr(selOffset));
line.push(nodeValue.substr(0, selOffset) + '\x01' + nodeValue.substr(selOffset));
} else {
const nodeValue = node.nodeValue;
line.push(nodeValue);
}
if(entities && nodeValue.trim()) {
if(node.parentNode) {
@ -80,7 +81,6 @@ export default function getRichElementValue(node: HTMLElement, lines: string[], @@ -80,7 +81,6 @@ export default function getRichElementValue(node: HTMLElement, lines: string[],
}
offset.offset += nodeValue.length;
}
return;
}

4
src/helpers/dom/getRichValueWithCaret.ts

@ -18,8 +18,8 @@ export default function getRichValueWithCaret(field: HTMLElement, withEntities = @@ -18,8 +18,8 @@ export default function getRichValueWithCaret(field: HTMLElement, withEntities =
const line: string[] = [];
const sel = window.getSelection();
var selNode
var selOffset
let selNode: Node;
let selOffset: number;
if(sel && sel.rangeCount) {
const range = sel.getRangeAt(0);
if(range.startContainer &&

46
src/helpers/dom/setRichFocus.ts

@ -0,0 +1,46 @@ @@ -0,0 +1,46 @@
/*
* 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 setRichFocus(field: HTMLElement, selectNode: Node, noCollapse?: boolean) {
field.focus();
if(selectNode &&
selectNode.parentNode == field &&
!selectNode.nextSibling &&
!noCollapse) {
field.removeChild(selectNode);
selectNode = null;
}
if(window.getSelection && document.createRange) {
const range = document.createRange();
if(selectNode) {
range.selectNode(selectNode);
} else {
range.selectNodeContents(field);
}
if(!noCollapse) {
range.collapse(false);
}
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
/* else if (document.body.createTextRange !== undefined) {
var textRange = document.body.createTextRange()
textRange.moveToElementText(selectNode || field)
if (!noCollapse) {
textRange.collapse(false)
}
textRange.select()
} */
}

6
src/lang.ts

@ -43,6 +43,11 @@ const lang = { @@ -43,6 +43,11 @@ const lang = {
},
"Chat.Search.NoMessagesFound": "No messages found",
"Chat.Search.PrivateSearch": "Private Search",
"ConnectionStatus.ReconnectIn": "Reconnect in %ds, %s",
"ConnectionStatus.Reconnect": "reconnect",
"ConnectionStatus.Waiting": "Waiting for network...",
"Deactivated.Title": "Too many tabs...",
"Deactivated.Subtitle": "Telegram supports only one active tab with the app.\nClick anywhere to continue using this tab.",
//"Saved": "Saved",
"General.Keyboard": "Keyboard",
"General.SendShortcut.Enter": "Send by Enter",
@ -430,6 +435,7 @@ const lang = { @@ -430,6 +435,7 @@ const lang = {
"Recent": "Recent",
"Of": "%1$d of %2$d",
"NoResult": "No results",
"Updating": "Updating...",
// * macos
"AccountSettings.Filters": "Chat Folders",

9
src/layer.d.ts vendored

@ -4025,7 +4025,7 @@ export namespace ReplyMarkup { @@ -4025,7 +4025,7 @@ export namespace ReplyMarkup {
/**
* @link https://core.telegram.org/type/MessageEntity
*/
export type MessageEntity = MessageEntity.messageEntityUnknown | MessageEntity.messageEntityMention | MessageEntity.messageEntityHashtag | MessageEntity.messageEntityBotCommand | MessageEntity.messageEntityUrl | MessageEntity.messageEntityEmail | MessageEntity.messageEntityBold | MessageEntity.messageEntityItalic | MessageEntity.messageEntityCode | MessageEntity.messageEntityPre | MessageEntity.messageEntityTextUrl | MessageEntity.messageEntityMentionName | MessageEntity.inputMessageEntityMentionName | MessageEntity.messageEntityPhone | MessageEntity.messageEntityCashtag | MessageEntity.messageEntityUnderline | MessageEntity.messageEntityStrike | MessageEntity.messageEntityBlockquote | MessageEntity.messageEntityBankCard | MessageEntity.messageEntityEmoji | MessageEntity.messageEntityHighlight | MessageEntity.messageEntityLinebreak;
export type MessageEntity = MessageEntity.messageEntityUnknown | MessageEntity.messageEntityMention | MessageEntity.messageEntityHashtag | MessageEntity.messageEntityBotCommand | MessageEntity.messageEntityUrl | MessageEntity.messageEntityEmail | MessageEntity.messageEntityBold | MessageEntity.messageEntityItalic | MessageEntity.messageEntityCode | MessageEntity.messageEntityPre | MessageEntity.messageEntityTextUrl | MessageEntity.messageEntityMentionName | MessageEntity.inputMessageEntityMentionName | MessageEntity.messageEntityPhone | MessageEntity.messageEntityCashtag | MessageEntity.messageEntityUnderline | MessageEntity.messageEntityStrike | MessageEntity.messageEntityBlockquote | MessageEntity.messageEntityBankCard | MessageEntity.messageEntityEmoji | MessageEntity.messageEntityHighlight | MessageEntity.messageEntityLinebreak | MessageEntity.messageEntityCaret;
export namespace MessageEntity {
export type messageEntityUnknown = {
@ -4164,6 +4164,12 @@ export namespace MessageEntity { @@ -4164,6 +4164,12 @@ export namespace MessageEntity {
offset?: number,
length?: number
};
export type messageEntityCaret = {
_: 'messageEntityCaret',
offset?: number,
length?: number
};
}
/**
@ -9649,6 +9655,7 @@ export interface ConstructorDeclMap { @@ -9649,6 +9655,7 @@ export interface ConstructorDeclMap {
'messageEntityEmoji': MessageEntity.messageEntityEmoji,
'messageEntityHighlight': MessageEntity.messageEntityHighlight,
'messageEntityLinebreak': MessageEntity.messageEntityLinebreak,
'messageEntityCaret': MessageEntity.messageEntityCaret,
'messageActionChatLeave': MessageAction.messageActionChatLeave,
'messageActionChannelDeletePhoto': MessageAction.messageActionChannelDeletePhoto,
'messageActionChannelEditTitle': MessageAction.messageActionChannelEditTitle,

23
src/lib/appManagers/appDialogsManager.ts

@ -32,7 +32,7 @@ import App from "../../config/app"; @@ -32,7 +32,7 @@ import App from "../../config/app";
import DEBUG, { MOUNT_CLASS_TO } from "../../config/debug";
import appNotificationsManager from "./appNotificationsManager";
import PeerTitle from "../../components/peerTitle";
import { i18n, _i18n } from "../langPack";
import { i18n, LangPackKey, _i18n } from "../langPack";
import findUpTag from "../../helpers/dom/findUpTag";
import { LazyLoadQueueIntersector } from "../../components/lazyLoadQueue";
import lottieLoader from "../lottieLoader";
@ -67,8 +67,9 @@ class ConnectionStatusComponent { @@ -67,8 +67,9 @@ class ConnectionStatusComponent {
private statusEl: HTMLElement;
private statusPreloader: ProgressivePreloader;
private currentText = '';
private currentLangPackKey = '';
private connectingTimeout: number;
private connecting = false;
private updating = false;
@ -151,23 +152,29 @@ class ConnectionStatusComponent { @@ -151,23 +152,29 @@ class ConnectionStatusComponent {
}
this.connecting = !online;
this.connectingTimeout = status && status.timeout;
DEBUG && this.log('connecting', this.connecting);
this.setState();
});
};
private setStatusText = (text: string) => {
if(this.currentText === text) return;
this.statusEl.innerText = this.currentText = text;
private setStatusText = (langPackKey: LangPackKey) => {
if(this.currentLangPackKey === langPackKey) return;
this.currentLangPackKey = langPackKey;
replaceContent(this.statusEl, i18n(langPackKey));
this.statusPreloader.attach(this.statusEl);
};
private setState = () => {
const timeout = ConnectionStatusComponent.CHANGE_STATE_DELAY;
if(this.connecting) {
this.setStatusText('Waiting for network...');
// if(this.connectingTimeout) {
// this.setStatusText('ConnectionStatus.Reconnect');
// } else {
this.setStatusText('ConnectionStatus.Waiting');
// }
} else if(this.updating) {
this.setStatusText('Updating...');
this.setStatusText('Updating');
}
DEBUG && this.log('setState', this.connecting || this.updating);
@ -908,7 +915,7 @@ export class AppDialogsManager { @@ -908,7 +915,7 @@ export class AppDialogsManager {
const offsetTop = this.folders.container.offsetTop;
const firstY = rectContainer.y + offsetTop;
const lastY = rectContainer.y - 8; // 8px - .chatlist padding-bottom
const lastY = rectContainer.y/* - 8 */; // 8px - .chatlist padding-bottom
const firstElement = findUpTag(document.elementFromPoint(Math.ceil(rectTarget.x), Math.ceil(firstY + 1)), firstElementChild.tagName) as HTMLElement;
const lastElement = findUpTag(document.elementFromPoint(Math.ceil(rectTarget.x), Math.floor(lastY + rectContainer.height - 1)), firstElementChild.tagName) as HTMLElement;

58
src/lib/appManagers/appEmojiManager.ts

@ -1,3 +1,9 @@ @@ -1,3 +1,9 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
import App from "../../config/app";
import { MOUNT_CLASS_TO } from "../../config/debug";
import { validateInitObject } from "../../helpers/object";
@ -6,6 +12,7 @@ import { isObject } from "../mtproto/bin_utils"; @@ -6,6 +12,7 @@ import { isObject } from "../mtproto/bin_utils";
import apiManager from "../mtproto/mtprotoworker";
import SearchIndex from "../searchIndex";
import sessionStorage from "../sessionStorage";
import appStateManager from "./appStateManager";
type EmojiLangPack = {
keywords: {
@ -21,7 +28,10 @@ const EMOJI_LANG_PACK: EmojiLangPack = { @@ -21,7 +28,10 @@ const EMOJI_LANG_PACK: EmojiLangPack = {
langCode: App.langPackCode
};
const RECENT_MAX_LENGTH = 36;
export class AppEmojiManager {
private static POPULAR_EMOJI = ["😂", "😘", "❤", "😍", "😊", "😁", "👍", "☺", "😔", "😄", "😭", "💋", "😒", "😳", "😜", "🙈", "😉", "😃", "😢", "😝", "😱", "😡", "😏", "😞", "😅", "😚", "🙊", "😌", "😀", "😋", "😆", "👌", "😐", "😕"];
private keywordLangPacks: {
[langCode: string]: EmojiLangPack
} = {};
@ -31,6 +41,9 @@ export class AppEmojiManager { @@ -31,6 +41,9 @@ export class AppEmojiManager {
private getKeywordsPromises: {[langCode: string]: Promise<EmojiLangPack>} = {};
private recent: string[];
private getRecentEmojisPromise: Promise<AppEmojiManager['recent']>;
/* public getPopularEmoji() {
return sessionStorage.get('emojis_popular').then(popEmojis => {
var result = []
@ -134,7 +147,7 @@ export class AppEmojiManager { @@ -134,7 +147,7 @@ export class AppEmojiManager {
}
public getBothEmojiKeywords() {
const promises: ReturnType<AppEmojiManager['getEmojiKeywords']>[] = [
const promises: Promise<any>[] = [
this.getEmojiKeywords()
];
@ -142,12 +155,16 @@ export class AppEmojiManager { @@ -142,12 +155,16 @@ export class AppEmojiManager {
promises.push(this.getEmojiKeywords(I18n.lastRequestedLangCode));
}
if(!this.recent) {
promises.push(this.getRecentEmojis());
}
return Promise.all(promises);
}
public indexEmojis() {
if(!this.index) {
this.index = new SearchIndex();
this.index = new SearchIndex(false, false);
}
for(const langCode in this.keywordLangPacks) {
@ -170,14 +187,47 @@ export class AppEmojiManager { @@ -170,14 +187,47 @@ export class AppEmojiManager {
public searchEmojis(q: string) {
this.indexEmojis();
q = q.toLowerCase().replace(/_/g, ' ');
//const perf = performance.now();
let emojis: Array<string>;
if(q.trim()) {
const set = this.index.search(q);
const flattened = Array.from(set).reduce((acc, v) => acc.concat(v), []);
const emojis = Array.from(new Set(flattened));
emojis = Array.from(set).reduce((acc, v) => acc.concat(v), []);
} else {
emojis = this.recent.concat(AppEmojiManager.POPULAR_EMOJI).slice(0, RECENT_MAX_LENGTH);
}
emojis = Array.from(new Set(emojis));
//console.log('searchEmojis', q, 'time', performance.now() - perf);
/* for(let i = 0, length = emojis.length; i < length; ++i) {
if(emojis[i].includes(zeroWidthJoiner) && !emojis[i].includes('\ufe0f')) {
emojis[i] += '\ufe0f';
}
} */
return emojis;
}
public getRecentEmojis() {
if(this.getRecentEmojisPromise) return this.getRecentEmojisPromise;
return this.getRecentEmojisPromise = appStateManager.getState().then(state => {
return this.recent = Array.isArray(state.recentEmoji) ? state.recentEmoji : [];
});
}
public pushRecentEmoji(emoji: string) {
this.getRecentEmojis().then(recent => {
recent.findAndSplice(e => e === emoji);
recent.unshift(emoji);
if(recent.length > RECENT_MAX_LENGTH) {
recent.length = RECENT_MAX_LENGTH;
}
appStateManager.pushToState('recentEmoji', recent);
});
}
}
const appEmojiManager = new AppEmojiManager();

36
src/lib/appManagers/appImManager.ts

@ -57,6 +57,8 @@ import placeCaretAtEnd from '../../helpers/dom/placeCaretAtEnd'; @@ -57,6 +57,8 @@ import placeCaretAtEnd from '../../helpers/dom/placeCaretAtEnd';
import replaceContent from '../../helpers/dom/replaceContent';
import whichChild from '../../helpers/dom/whichChild';
import appEmojiManager from './appEmojiManager';
import PopupElement from '../../components/popups';
import singleInstance from '../mtproto/singleInstance';
//console.log('appImManager included33!');
@ -181,6 +183,38 @@ export class AppImManager { @@ -181,6 +183,38 @@ export class AppImManager {
this.applyCurrentTheme();
});
rootScope.on('instance_deactivated', () => {
const popup = new PopupElement('popup-instance-deactivated', undefined, {overlayClosable: true});
const c = document.createElement('div');
c.classList.add('instance-deactivated-container');
(popup as any).container.replaceWith(c);
const header = document.createElement('div');
header.classList.add('header');
header.append(i18n('Deactivated.Title'));
const subtitle = document.createElement('div');
subtitle.classList.add('subtitle');
subtitle.append(i18n('Deactivated.Subtitle'));
c.append(header, subtitle);
document.body.classList.add('deactivated');
(popup as any).onClose = () => {
document.body.classList.add('deactivated-backwards');
singleInstance.reset();
singleInstance.checkInstance(false);
setTimeout(() => {
document.body.classList.remove('deactivated', 'deactivated-backwards');
}, 333);
};
popup.show();
});
sessionStorage.get('chatPositions').then((c) => {
sessionStorage.setToCache('chatPositions', c || {});
});
@ -723,7 +757,7 @@ export class AppImManager { @@ -723,7 +757,7 @@ export class AppImManager {
if(!this.myId) return Promise.resolve();
appUsersManager.setUserStatus(this.myId, this.offline);
return apiManager.invokeApi('account.updateStatus', {offline: this.offline});
return apiManager.invokeApiSingle('account.updateStatus', {offline: this.offline});
}
private createNewChat() {

22
src/lib/appManagers/appMessagesManager.ts

@ -1654,6 +1654,11 @@ export class AppMessagesManager { @@ -1654,6 +1654,11 @@ export class AppMessagesManager {
telegramMeWebService.setAuthorized(true);
} */
// can reset here pinned order
if(!offsetId && !offsetDate && !offsetPeerId) {
this.dialogsStorage.resetPinnedOrder(folderId);
}
appUsersManager.saveApiUsers(dialogsResult.users);
appChatsManager.saveApiChats(dialogsResult.chats);
this.saveMessages(dialogsResult.messages);
@ -1666,6 +1671,12 @@ export class AppMessagesManager { @@ -1666,6 +1671,12 @@ export class AppMessagesManager {
// ! нужно передавать folderId, так как по папке !== 0 нет свойства folder_id
this.dialogsStorage.saveDialog(dialog, dialog.folder_id ?? folderId);
if(!maxSeenIdIncremented &&
!appPeersManager.isChannel(appPeersManager.getPeerId(dialog.peer))) {
this.incrementMaxSeenId(dialog.top_message);
maxSeenIdIncremented = true;
}
if(dialog.peerId === undefined) {
return;
}
@ -1690,12 +1701,6 @@ export class AppMessagesManager { @@ -1690,12 +1701,6 @@ export class AppMessagesManager {
this.log.error('lun bot', folderId);
} */
}
if(!maxSeenIdIncremented &&
!appPeersManager.isChannel(appPeersManager.getPeerId(dialog.peer))) {
this.incrementMaxSeenId(dialog.top_message);
maxSeenIdIncremented = true;
}
});
if(Object.keys(noIdsDialogs).length) {
@ -1712,7 +1717,7 @@ export class AppMessagesManager { @@ -1712,7 +1717,7 @@ export class AppMessagesManager {
const count = (dialogsResult as MessagesDialogs.messagesDialogsSlice).count;
if(!dialogsResult.dialogs.length ||
if(limit > dialogsResult.dialogs.length ||
!count ||
dialogs.length >= count) {
this.dialogsStorage.setDialogsLoaded(folderId, true);
@ -4540,7 +4545,8 @@ export class AppMessagesManager { @@ -4540,7 +4545,8 @@ export class AppMessagesManager {
delete historyStorage.maxId;
slice.unsetEnd(SliceEnd.Bottom);
let historyResult = this.getHistory(peerId, slice[0], 0, 50, threadId);
// if there is no id - then request by first id because cannot request by id 0 with backLimit
let historyResult = this.getHistory(peerId, slice[0] ?? 1, 0, 50, threadId);
if(historyResult instanceof Promise) {
historyResult = await historyResult;
}

1
src/lib/appManagers/appPhotosManager.ts

@ -240,6 +240,7 @@ export class AppPhotosManager { @@ -240,6 +240,7 @@ export class AppPhotosManager {
if(message &&
(message.message ||
message.reply_to_mid ||
message.media.webpage ||
(message.replies && message.replies.pFlags.comments && message.replies.channel_id !== 777)
)

21
src/lib/appManagers/appStateManager.ts

@ -22,6 +22,7 @@ import { Chat } from '../../layer'; @@ -22,6 +22,7 @@ import { Chat } from '../../layer';
import { isMobile } from '../../helpers/userAgent';
const REFRESH_EVERY = 24 * 60 * 60 * 1000; // 1 day
const REFRESH_EVERY_WEEK = 24 * 60 * 60 * 1000 * 7; // 7 days
const STATE_VERSION = App.version;
export type Background = {
@ -145,8 +146,10 @@ export const STATE_INIT: State = { @@ -145,8 +146,10 @@ export const STATE_INIT: State = {
const ALL_KEYS = Object.keys(STATE_INIT) as any as Array<keyof State>;
const REFRESH_KEYS = ['dialogs', 'allDialogsLoaded', 'messages', 'contactsList', 'stateCreatedTime',
'updates', 'maxSeenMsgId', 'filters', 'topPeers', 'pinnedOrders'] as any as Array<keyof State>;
const REFRESH_KEYS = ['contactsList', 'stateCreatedTime',
'maxSeenMsgId', 'filters', 'topPeers'] as any as Array<keyof State>;
const REFRESH_KEYS_WEEK = ['dialogs', 'allDialogsLoaded', 'updates', 'pinnedOrders'] as any as Array<keyof State>;
export class AppStateManager extends EventListenerBase<{
save: (state: State) => Promise<void>,
@ -266,7 +269,8 @@ export class AppStateManager extends EventListenerBase<{ @@ -266,7 +269,8 @@ export class AppStateManager extends EventListenerBase<{
this.log('will refresh state', state.stateCreatedTime, time);
}
REFRESH_KEYS.forEach(key => {
const r = (keys: typeof REFRESH_KEYS) => {
keys.forEach(key => {
this.pushToState(key, copy(STATE_INIT[key]));
// @ts-ignore
@ -275,6 +279,17 @@ export class AppStateManager extends EventListenerBase<{ @@ -275,6 +279,17 @@ export class AppStateManager extends EventListenerBase<{
s.length = 0;
}
});
};
r(REFRESH_KEYS);
if((state.stateCreatedTime + REFRESH_EVERY_WEEK) < time) {
if(DEBUG) {
this.log('will refresh updates');
}
r(REFRESH_KEYS_WEEK);
}
}
//state = this.state = new Proxy(state, getHandler());

6
src/lib/config.ts

File diff suppressed because one or more lines are too long

8
src/lib/mtproto/apiManager.ts

@ -67,7 +67,7 @@ export type ApiError = Partial<{ @@ -67,7 +67,7 @@ export type ApiError = Partial<{
} */
export class ApiManager {
public cachedNetworkers: {
private cachedNetworkers: {
[transportType in TransportType]: {
[connectionType in ConnectionType]: {
[dcId: number]: MTPNetworker[]
@ -75,9 +75,9 @@ export class ApiManager { @@ -75,9 +75,9 @@ export class ApiManager {
}
} = {} as any;
public cachedExportPromise: {[x: number]: Promise<unknown>} = {};
private cachedExportPromise: {[x: number]: Promise<unknown>} = {};
private gettingNetworkers: {[dcIdAndType: string]: Promise<MTPNetworker>} = {};
public baseDcId = 0;
private baseDcId = 0;
//public telegramMeNotified = false;
@ -288,7 +288,9 @@ export class ApiManager { @@ -288,7 +288,9 @@ export class ApiManager {
const startTime = Date.now();
const interval = ctx.setInterval(() => {
if(!cachedNetworker || !cachedNetworker.isStopped()) {
this.log.error('Request is still processing:', method, params, options, 'time:', (Date.now() - startTime) / 1000);
}
//this.cachedUploadNetworkers[2].requestMessageStatus();
}, 5e3);
}

8
src/lib/mtproto/mtproto.service.ts

@ -52,7 +52,7 @@ const onFetch = (event: FetchEvent): void => { @@ -52,7 +52,7 @@ const onFetch = (event: FetchEvent): void => {
try {
const [, url, scope, params] = /http[:s]+\/\/.*?(\/(.*?)(?:$|\/(.*)$))/.exec(event.request.url) || [];
log.debug('[fetch]:', event);
//log.debug('[fetch]:', event);
switch(scope) {
case 'stream': {
@ -71,7 +71,7 @@ const onFetch = (event: FetchEvent): void => { @@ -71,7 +71,7 @@ const onFetch = (event: FetchEvent): void => {
offset = info.size - (info.size % limitPart);
} */
log.debug('[stream]', url, offset, end);
//log.debug('[stream]', url, offset, end);
event.respondWith(Promise.race([
timeout(45 * 1000),
@ -86,7 +86,7 @@ const onFetch = (event: FetchEvent): void => { @@ -86,7 +86,7 @@ const onFetch = (event: FetchEvent): void => {
const limit = end && end < limitPart ? alignLimit(end - offset + 1) : limitPart;
const alignedOffset = alignOffset(offset, limit);
log.debug('[stream] requestFilePart:', /* info.dcId, info.location, */ alignedOffset, limit);
//log.debug('[stream] requestFilePart:', /* info.dcId, info.location, */ alignedOffset, limit);
const task: ServiceWorkerTask = {
type: 'requestFilePart',
@ -99,7 +99,7 @@ const onFetch = (event: FetchEvent): void => { @@ -99,7 +99,7 @@ const onFetch = (event: FetchEvent): void => {
deferred.then(result => {
let ab = result.bytes as Uint8Array;
log.debug('[stream] requestFilePart result:', result);
//log.debug('[stream] requestFilePart result:', result);
const headers: Record<string, string> = {
'Accept-Ranges': 'bytes',

7
src/lib/mtproto/mtproto.worker.ts

@ -131,6 +131,13 @@ const onMessage = async(e: any) => { @@ -131,6 +131,13 @@ const onMessage = async(e: any) => {
break;
}
case 'startAll':
case 'stopAll': {
// @ts-ignore
networkerFactory[task.task].apply(networkerFactory);
break;
}
default: {
try {
// @ts-ignore

11
src/lib/mtproto/mtprotoworker.ts

@ -20,6 +20,7 @@ import type { MTMessage } from './networker'; @@ -20,6 +20,7 @@ import type { MTMessage } from './networker';
import DEBUG, { MOUNT_CLASS_TO } from '../../config/debug';
import Socket from './transports/websocket';
import IDBStorage from '../idb';
import singleInstance from './singleInstance';
type Task = {
taskId: number,
@ -86,6 +87,8 @@ export class ApiManagerProxy extends CryptoWorkerMethods { @@ -86,6 +87,8 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
super();
this.log('constructor');
singleInstance.start();
this.registerServiceWorker();
this.addTaskListener('clear', () => {
@ -482,6 +485,14 @@ export class ApiManagerProxy extends CryptoWorkerMethods { @@ -482,6 +485,14 @@ export class ApiManagerProxy extends CryptoWorkerMethods {
public toggleStorage(enabled: boolean) {
return this.performTaskWorker('toggleStorage', enabled);
}
public stopAll() {
return this.performTaskWorker('stopAll');
}
public startAll() {
return this.performTaskWorker('startAll');
}
}
const apiManagerProxy = new ApiManagerProxy();

63
src/lib/mtproto/networker.ts

@ -188,7 +188,7 @@ export default class MTPNetworker { @@ -188,7 +188,7 @@ export default class MTPNetworker {
}
}
public updateSession() {
private updateSession() {
this.seqNo = 0;
this.prevSessionId = this.sessionId;
this.sessionId = new Uint8Array(8).randomize();
@ -203,7 +203,7 @@ export default class MTPNetworker { @@ -203,7 +203,7 @@ export default class MTPNetworker {
}
} */
public updateSentMessage(sentMessageId: string) {
private updateSentMessage(sentMessageId: string) {
const sentMessage = this.sentMessages[sentMessageId];
if(!sentMessage) {
return false;
@ -233,7 +233,7 @@ export default class MTPNetworker { @@ -233,7 +233,7 @@ export default class MTPNetworker {
return sentMessage;
}
public generateSeqNo(notContentRelated?: boolean) {
private generateSeqNo(notContentRelated?: boolean) {
let seqNo = this.seqNo * 2;
if(!notContentRelated) {
@ -471,11 +471,12 @@ export default class MTPNetworker { @@ -471,11 +471,12 @@ export default class MTPNetworker {
// };
/// #if MTPROTO_HTTP || MTPROTO_HTTP_UPLOAD
public checkLongPoll = () => {
private checkLongPoll = () => {
const isClean = this.cleanupSent();
//this.log.error('Check lp', this.longPollPending, this.dcId, isClean, this);
if((this.longPollPending && Date.now() < this.longPollPending) ||
this.offline) {
this.offline ||
this.isStopped()) {
//this.log('No lp this time');
return false;
}
@ -494,7 +495,7 @@ export default class MTPNetworker { @@ -494,7 +495,7 @@ export default class MTPNetworker {
});
};
public sendLongPoll() {
private sendLongPoll() {
const maxWait = 25000;
this.longPollPending = Date.now() + maxWait;
@ -515,7 +516,7 @@ export default class MTPNetworker { @@ -515,7 +516,7 @@ export default class MTPNetworker {
});
}
public checkConnection = (event: Event | string) => {
private checkConnection = (event: Event | string) => {
/* rootScope.offlineConnecting = true */
this.log('Check connection', event);
@ -548,7 +549,7 @@ export default class MTPNetworker { @@ -548,7 +549,7 @@ export default class MTPNetworker {
});
};
public toggleOffline(enabled: boolean) {
private toggleOffline(enabled: boolean) {
// this.log('toggle ', enabled, this.dcId, this.iii)
if(this.offline !== undefined && this.offline === enabled) {
return false;
@ -642,7 +643,7 @@ export default class MTPNetworker { @@ -642,7 +643,7 @@ export default class MTPNetworker {
/// #endif
// тут можно сделать таймаут и выводить дисконнект
public pushMessage(message: {
private pushMessage(message: {
msg_id: string,
seq_no: number,
body: Uint8Array | number[],
@ -692,7 +693,7 @@ export default class MTPNetworker { @@ -692,7 +693,7 @@ export default class MTPNetworker {
return promise;
}
public setConnectionStatus(online: boolean) {
public setConnectionStatus(online: boolean, timeout?: number) {
const willChange = this.isOnline !== online;
this.isOnline = online;
@ -705,10 +706,15 @@ export default class MTPNetworker { @@ -705,10 +706,15 @@ export default class MTPNetworker {
name: this.name,
isFileNetworker: this.isFileNetworker,
isFileDownload: this.isFileDownload,
isFileUpload: this.isFileUpload
isFileUpload: this.isFileUpload,
timeout
});
}
if(this.isOnline) {
this.scheduleRequest();
}
// if((this.transport as TcpObfuscated).networker) {
// this.sendPingDelayDisconnect();
// }
@ -720,7 +726,7 @@ export default class MTPNetworker { @@ -720,7 +726,7 @@ export default class MTPNetworker {
} */
}
public pushResend(messageId: string, delay = 100) {
private pushResend(messageId: string, delay = 100) {
const value = delay ? Date.now() + delay : 0;
const sentMessage = this.sentMessages[messageId];
if(sentMessage.container) {
@ -743,7 +749,7 @@ export default class MTPNetworker { @@ -743,7 +749,7 @@ export default class MTPNetworker {
}
// * correct, fully checked
public async getMsgKey(dataWithPadding: ArrayBuffer, isOut: boolean) {
private async getMsgKey(dataWithPadding: ArrayBuffer, isOut: boolean) {
const x = isOut ? 0 : 8;
const msgKeyLargePlain = bufferConcat(this.authKeyUint8.subarray(88 + x, 88 + x + 32), dataWithPadding);
@ -753,7 +759,7 @@ export default class MTPNetworker { @@ -753,7 +759,7 @@ export default class MTPNetworker {
};
// * correct, fully checked
public getAesKeyIv(msgKey: Uint8Array | number[], isOut: boolean): Promise<[Uint8Array, Uint8Array]> {
private getAesKeyIv(msgKey: Uint8Array | number[], isOut: boolean): Promise<[Uint8Array, Uint8Array]> {
const x = isOut ? 0 : 8;
const sha2aText = new Uint8Array(52);
const sha2bText = new Uint8Array(52);
@ -785,9 +791,17 @@ export default class MTPNetworker { @@ -785,9 +791,17 @@ export default class MTPNetworker {
});
}
public isStopped() {
return NetworkerFactory.akStopped && !this.isFileNetworker;
}
private performScheduledRequest() {
// this.log('scheduled', this.dcId, this.iii)
if(this.isStopped()) {
return false;
}
if(this.pendingAcks.length) {
const ackMsgIds: Array<string> = this.pendingAcks.slice();
@ -936,7 +950,7 @@ export default class MTPNetworker { @@ -936,7 +950,7 @@ export default class MTPNetworker {
if(lengthOverflow) {
this.scheduleRequest();
}
};
}
private generateContainerMessage(messagesByteLen: number, messages: MTMessage[]) {
const container = new TLSerialization({
@ -973,7 +987,7 @@ export default class MTPNetworker { @@ -973,7 +987,7 @@ export default class MTPNetworker {
};
}
public async getEncryptedMessage(dataWithPadding: ArrayBuffer) {
private async getEncryptedMessage(dataWithPadding: ArrayBuffer) {
const msgKey = await this.getMsgKey(dataWithPadding, true);
const keyIv = await this.getAesKeyIv(msgKey, true);
// this.log('after msg key iv')
@ -987,7 +1001,7 @@ export default class MTPNetworker { @@ -987,7 +1001,7 @@ export default class MTPNetworker {
};
}
public getDecryptedMessage(msgKey: Uint8Array, encryptedData: Uint8Array): Promise<ArrayBuffer> {
private getDecryptedMessage(msgKey: Uint8Array, encryptedData: Uint8Array): Promise<ArrayBuffer> {
// this.log('get decrypted start')
return this.getAesKeyIv(msgKey, false).then((keyIv) => {
// this.log('after msg key iv')
@ -995,7 +1009,7 @@ export default class MTPNetworker { @@ -995,7 +1009,7 @@ export default class MTPNetworker {
});
}
public getEncryptedOutput(message: MTMessage) {
private getEncryptedOutput(message: MTMessage) {
/* if(DEBUG) {
this.log.debug('Send encrypted', message, this.authKeyId);
} */
@ -1088,7 +1102,7 @@ export default class MTPNetworker { @@ -1088,7 +1102,7 @@ export default class MTPNetworker {
});
}
public sendEncryptedRequest(message: MTMessage) {
private sendEncryptedRequest(message: MTMessage) {
return this.getEncryptedOutput(message).then(requestData => {
this.debug && this.log.debug('sendEncryptedRequest: launching message into space:', message, [message.msg_id].concat(message.inner || []));
@ -1242,7 +1256,7 @@ export default class MTPNetworker { @@ -1242,7 +1256,7 @@ export default class MTPNetworker {
});
}
public applyServerSalt(newServerSalt: string) {
private applyServerSalt(newServerSalt: string) {
const serverSalt = longToBytes(newServerSalt);
sessionStorage.set({
@ -1313,7 +1327,7 @@ export default class MTPNetworker { @@ -1313,7 +1327,7 @@ export default class MTPNetworker {
}
}
public ackMessage(msgId: string) {
private ackMessage(msgId: string) {
// this.log('ack message', msgID)
this.pendingAcks.push(msgId);
@ -1324,7 +1338,7 @@ export default class MTPNetworker { @@ -1324,7 +1338,7 @@ export default class MTPNetworker {
/// #endif
}
public reqResendMessage(msgId: string) {
private reqResendMessage(msgId: string) {
if(this.debug) {
this.log.debug('Req resend', msgId);
}
@ -1361,7 +1375,7 @@ export default class MTPNetworker { @@ -1361,7 +1375,7 @@ export default class MTPNetworker {
return !notEmpty;
}
public processMessageAck(messageId: string) {
private processMessageAck(messageId: string) {
const sentMessage = this.sentMessages[messageId];
if(sentMessage && !sentMessage.acked) {
//delete sentMessage.body;
@ -1369,7 +1383,7 @@ export default class MTPNetworker { @@ -1369,7 +1383,7 @@ export default class MTPNetworker {
}
}
public processError(rawError: {error_message: string, error_code: number}) {
private processError(rawError: {error_message: string, error_code: number}) {
const matches = (rawError.error_message || '').match(/^([A-Z_0-9]+\b)(: (.+))?/) || [];
rawError.error_code = rawError.error_code;
@ -1518,7 +1532,6 @@ export default class MTPNetworker { @@ -1518,7 +1532,6 @@ export default class MTPNetworker {
break;
}
case 'new_session_created': {
this.ackMessage(messageId);

11
src/lib/mtproto/networkerFactory.ts

@ -14,6 +14,7 @@ import { ConnectionStatusChange, InvokeApiOptions } from "../../types"; @@ -14,6 +14,7 @@ import { ConnectionStatusChange, InvokeApiOptions } from "../../types";
import MTTransport from "./transports/transport";
export class NetworkerFactory {
private networkers: MTPNetworker[] = [];
public updatesProcessor: (obj: any) => void = null;
public onConnectionStatusChange: (info: ConnectionStatusChange) => void = null;
public akStopped = false;
@ -24,13 +25,21 @@ export class NetworkerFactory { @@ -24,13 +25,21 @@ export class NetworkerFactory {
public getNetworker(dcId: number, authKey: number[], authKeyID: Uint8Array, serverSalt: number[], transport: MTTransport, options: InvokeApiOptions) {
//console.log('NetworkerFactory: creating new instance of MTPNetworker:', dcId, options);
return new MTPNetworker(dcId, authKey, authKeyID, serverSalt, transport, options);
const networker = new MTPNetworker(dcId, authKey, authKeyID, serverSalt, transport, options);
this.networkers.push(networker);
return networker;
}
public startAll() {
if(this.akStopped) {
const stoppedNetworkers = this.networkers.filter(networker => networker.isStopped());
this.akStopped = false;
this.updatesProcessor && this.updatesProcessor({_: 'new_session_created'});
for(const networker of stoppedNetworkers) {
networker.scheduleRequest();
}
}
}

56
src/lib/mtproto/singleInstance.ts

@ -3,6 +3,7 @@ import { nextRandomInt } from "../../helpers/random"; @@ -3,6 +3,7 @@ import { nextRandomInt } from "../../helpers/random";
import { logger } from "../logger";
import rootScope from "../rootScope";
import sessionStorage from "../sessionStorage";
import apiManager from "./mtprotoworker";
export type AppInstance = {
id: number,
@ -10,23 +11,28 @@ export type AppInstance = { @@ -10,23 +11,28 @@ export type AppInstance = {
time: number
};
const CHECK_INSTANCE_INTERVAL = 5000;
const DEACTIVATE_TIMEOUT = 30000;
const MULTIPLE_TABS_THRESHOLD = 20000;
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');
private instanceID: number;
private started: boolean;
private masterInstance: boolean;
private deactivateTimeout: number;
private deactivated: boolean;
private initial: boolean;
private log = logger('INSTANCE');
public start() {
if(!this.started/* && !Config.Navigator.mobile && !Config.Modes.packed */) {
this.started = true
this.started = true;
this.reset();
//IdleManager.start();
rootScope.addEventListener('idle', this.checkInstance);
setInterval(this.checkInstance, 5000);
setInterval(this.checkInstance, CHECK_INSTANCE_INTERVAL);
this.checkInstance();
try {
@ -35,12 +41,21 @@ export class SingleInstance { @@ -35,12 +41,21 @@ export class SingleInstance {
}
}
public clearInstance() {
public reset() {
this.instanceID = nextRandomInt(0xFFFFFFFF);
this.masterInstance = false;
if(this.deactivateTimeout) clearTimeout(this.deactivateTimeout);
this.deactivateTimeout = 0;
this.deactivated = false;
this.initial = false;
}
public clearInstance = () => {
if(this.masterInstance && !this.deactivated) {
this.log.warn('clear master instance');
sessionStorage.delete('xt_instance');
}
}
};
public deactivateInstance = () => {
if(this.masterInstance || this.deactivated) {
@ -56,30 +71,31 @@ export class SingleInstance { @@ -56,30 +71,31 @@ export class SingleInstance {
//document.title = _('inactive_tab_title_raw')
rootScope.idle.deactivated = true;
rootScope.dispatchEvent('instance_deactivated');
};
public checkInstance = () => {
public checkInstance = (idle = rootScope.idle && rootScope.idle.isIDLE) => {
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)
sessionStorage.get('xt_instance', false).then((curInstance: AppInstance) => {
// this.log('check instance', newInstance, curInstance)
if(!idle ||
!curInstance ||
curInstance.id == this.instanceID ||
curInstance.time < time - 20000) {
curInstance.id === this.instanceID ||
curInstance.time < (time - MULTIPLE_TABS_THRESHOLD)) {
sessionStorage.set({xt_instance: newInstance});
if(!this.masterInstance) {
//MtpNetworkerFactory.startAll();
apiManager.startAll();
if(!this.initial) {
this.initial = true;
} else {
@ -95,10 +111,10 @@ export class SingleInstance { @@ -95,10 +111,10 @@ export class SingleInstance {
}
} else {
if(this.masterInstance) {
//MtpNetworkerFactory.stopAll();
apiManager.stopAll();
this.log.warn('now idle instance', newInstance);
if(!this.deactivateTimeout) {
this.deactivateTimeout = window.setTimeout(this.deactivateInstance, 30000);
this.deactivateTimeout = window.setTimeout(this.deactivateInstance, DEACTIVATE_TIMEOUT);
}
this.masterInstance = false;

11
src/lib/mtproto/transports/tcpObfuscated.ts

@ -28,11 +28,16 @@ export default class TcpObfuscated implements MTTransport { @@ -28,11 +28,16 @@ export default class TcpObfuscated implements MTTransport {
private log: ReturnType<typeof logger>;
public connected = false;
private lastCloseTime: number;
public connection: MTConnection;
private connection: MTConnection;
//private debugPayloads: MTPNetworker['debugRequests'] = [];
constructor(private Connection: MTConnectionConstructable, private dcId: number, private url: string, private logSuffix: string, public retryTimeout: number) {
constructor(private Connection: MTConnectionConstructable,
private dcId: number,
private url: string,
private logSuffix: string,
private retryTimeout: number
) {
let logTypes = LogTypes.Error | LogTypes.Log;
if(this.debug) logTypes |= LogTypes.Debug;
this.log = logger(`TCP-${dcId}` + logSuffix, logTypes);
@ -115,7 +120,7 @@ export default class TcpObfuscated implements MTTransport { @@ -115,7 +120,7 @@ export default class TcpObfuscated implements MTTransport {
const needTimeout = !isNaN(diff) && diff < this.retryTimeout ? this.retryTimeout - diff : 0;
if(this.networker) {
this.networker.setConnectionStatus(false);
this.networker.setConnectionStatus(false, needTimeout);
this.pending.length = 0;
}

21
src/lib/richtextprocessor.ts

@ -106,7 +106,8 @@ const markdownEntities: {[markdown: string]: MessageEntity['_']} = { @@ -106,7 +106,8 @@ const markdownEntities: {[markdown: string]: MessageEntity['_']} = {
const passConflictingEntities: Set<MessageEntity['_']> = new Set([
'messageEntityEmoji',
'messageEntityLinebreak'
'messageEntityLinebreak',
'messageEntityCaret'
]);
for(let i in markdownEntities) {
passConflictingEntities.add(markdownEntities[i]);
@ -116,19 +117,20 @@ namespace RichTextProcessor { @@ -116,19 +117,20 @@ namespace RichTextProcessor {
export const emojiSupported = navigator.userAgent.search(/OS X|iPhone|iPad|iOS/i) !== -1/* && false *//* || true */;
export function getEmojiSpritesheetCoords(emojiCode: string) {
let unified = encodeEmoji(emojiCode)/* .replace(/(-fe0f|fe0f)/g, '') */;
let unified = encodeEmoji(emojiCode);
if(unified === '1f441-200d-1f5e8') {
unified = '1f441-fe0f-200d-1f5e8-fe0f';
//unified = '1f441-fe0f-200d-1f5e8-fe0f';
unified = '1f441-fe0f-200d-1f5e8';
}
if(!emojiData.hasOwnProperty(unified)/* && !emojiData.hasOwnProperty(unified.replace(/(-fe0f|fe0f)/g, '')) */) {
if(!emojiData.hasOwnProperty(unified) && !emojiData.hasOwnProperty(unified.replace(/-?fe0f$/, ''))/* && !emojiData.hasOwnProperty(unified.replace(/(-fe0f|fe0f)/g, '')) */) {
//if(!emojiData.hasOwnProperty(emojiCode) && !emojiData.hasOwnProperty(emojiCode.replace(/[\ufe0f\u200d]/g, ''))) {
//console.error('lol', unified);
return null;
}
return unified.replace(/(-fe0f|fe0f)/g, '');
return unified.replace(/-?fe0f/g, '');
}
export function parseEntities(text: string) {
@ -138,6 +140,7 @@ namespace RichTextProcessor { @@ -138,6 +140,7 @@ namespace RichTextProcessor {
let matchIndex;
let rawOffset = 0;
// var start = tsNow()
fullRegExp.lastIndex = 0;
while((match = raw.match(fullRegExp))) {
matchIndex = rawOffset + match.index;
@ -541,6 +544,11 @@ namespace RichTextProcessor { @@ -541,6 +544,11 @@ namespace RichTextProcessor {
break;
}
case 'messageEntityCaret': {
insertPart(entity, '<span class="composer-sel"></span>');
break;
}
/* case 'messageEntityLinebreak': {
if(options.noLinebreaks) {
insertPart(entity, ' ');
@ -587,7 +595,7 @@ namespace RichTextProcessor { @@ -587,7 +595,7 @@ namespace RichTextProcessor {
const target = (currentContext || typeof electronHelpers !== 'undefined')
? '' : ' target="_blank" rel="noopener noreferrer"';
insertPart(entity, `<a class="anchor-url" href="${href}"${target}${masked ? 'onclick="showMaskedAlert(this)"' : ''}>`, '</a>');
insertPart(entity, `<a class="anchor-url" href="${href}"${target}${masked && !currentContext ? 'onclick="showMaskedAlert(this)"' : ''}>`, '</a>');
}
break;
@ -714,6 +722,7 @@ namespace RichTextProcessor { @@ -714,6 +722,7 @@ namespace RichTextProcessor {
var raw = text;
var text: any = [],
emojiTitle;
fullRegExp.lastIndex = 0;
while((match = raw.match(fullRegExp))) {
text.push(raw.substr(0, match.index))
if(match[8]) {

2
src/lib/rootScope.ts

@ -111,6 +111,8 @@ export type BroadcastEvents = { @@ -111,6 +111,8 @@ export type BroadcastEvents = {
'language_change': void,
'theme_change': void,
'instance_deactivated': void
};
export class RootScope extends EventListenerBase<{

10
src/lib/searchIndex.ts

@ -14,12 +14,16 @@ import cleanSearchText from '../helpers/cleanSearchText'; @@ -14,12 +14,16 @@ import cleanSearchText from '../helpers/cleanSearchText';
export default class SearchIndex<SearchWhat> {
private fullTexts: Map<SearchWhat, string> = new Map();
constructor(private cleanText = true, private latinize = true) {
}
public indexObject(id: SearchWhat, searchText: string) {
/* if(searchIndex.fullTexts.hasOwnProperty(id)) {
return false;
} */
if(searchText.trim()) {
if(searchText.trim() && this.cleanText) {
searchText = cleanSearchText(searchText);
}
@ -49,7 +53,9 @@ export default class SearchIndex<SearchWhat> { @@ -49,7 +53,9 @@ export default class SearchIndex<SearchWhat> {
const fullTexts = this.fullTexts;
//const shortIndexes = searchIndex.shortIndexes;
query = cleanSearchText(query);
if(this.cleanText) {
query = cleanSearchText(query, this.latinize);
}
const newFoundObjs: Array<{fullText: string, what: SearchWhat}> = [];
const queryWords = query.split(' ');

4
src/lib/storage.ts

@ -143,8 +143,8 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex @@ -143,8 +143,8 @@ export default class AppStorage<Storage extends Record<string, any>/* Storage ex
return this.cache[key] = value;
}
public async get(key: keyof Storage): Promise<Storage[typeof key]> {
if(this.cache.hasOwnProperty(key)) {
public async get(key: keyof Storage, useCache = true): Promise<Storage[typeof key]> {
if(this.cache.hasOwnProperty(key) && useCache) {
return this.getFromCache(key);
} else if(this.useStorage) {
const r = this.getPromises.get(key);

4
src/lib/storages/dialogs.ts

@ -133,6 +133,10 @@ export default class DialogsStorage { @@ -133,6 +133,10 @@ export default class DialogsStorage {
this.dialogsNum = 0;
}
public resetPinnedOrder(folderId: number) {
this.pinnedOrders[folderId] = [];
}
public getOffsetDate(folderId: number) {
return this.dialogsOffsetDate[folderId] || 0;
}

9
src/scripts/format_jsons.js

@ -12,6 +12,10 @@ let countries = require('fs').readFileSync('./in/countries.dat').toString(); @@ -12,6 +12,10 @@ let countries = require('fs').readFileSync('./in/countries.dat').toString();
//console.log(emoji, countries);
const path = process.argv[2];
const writePathTo = (/* path || */__dirname + '/out/');
console.log('Writing to:', writePathTo);
let formatted = emoji.filter(e => e.has_img_apple);
function encodeEmoji(emojiText) {
@ -150,6 +154,7 @@ if(false) { @@ -150,6 +154,7 @@ if(false) {
emoji = encodeEmoji(emoji);
//emoji = emoji.replace(/(-fe0f|fe0f)/g, '');
emoji = emoji.replace(/-?fe0f$/, '');
let c = categories[category] === undefined ? 9 : categories[category];
//obj[emoji] = '' + c + sort_order;
@ -159,7 +164,7 @@ if(false) { @@ -159,7 +164,7 @@ if(false) {
console.log(obj);
require('fs').writeFileSync('./out/emoji.json', JSON.stringify(obj));
require('fs').writeFileSync(writePathTo + 'emoji.json', JSON.stringify(obj));
}
/* {
@ -209,5 +214,5 @@ if(false) { @@ -209,5 +214,5 @@ if(false) {
//console.log(item);
});
require('fs').writeFileSync('./out/countries.json', JSON.stringify(arr));
require('fs').writeFileSync(writePathTo + 'countries.json', JSON.stringify(arr));
}

20804
src/scripts/in/emoji_pretty.json

File diff suppressed because it is too large Load Diff

7
src/scripts/in/schema_additional_params.json

@ -94,6 +94,13 @@ @@ -94,6 +94,13 @@
{"name": "length", "type": "number"}
],
"type": "MessageEntity"
}, {
"predicate": "messageEntityCaret",
"params": [
{"name": "offset", "type": "number"},
{"name": "length", "type": "number"}
],
"type": "MessageEntity"
}, {
"predicate": "user",
"params": [

2
src/scripts/out/countries.json

File diff suppressed because one or more lines are too long

2
src/scripts/out/emoji.json

File diff suppressed because one or more lines are too long

5
src/scss/partials/_chatBubble.scss

@ -824,9 +824,12 @@ $bubble-margin: .25rem; @@ -824,9 +824,12 @@ $bubble-margin: .25rem;
margin: 0 4px 6px 4px;
cursor: pointer;
border-radius: 4px;
min-width: 10rem;
&-content {
max-width: 300px;
//max-width: 300px;
position: absolute;
max-width: calc(100% - 24px);
}
}

4
src/scss/partials/_chatEmojiHelper.scss

@ -16,4 +16,8 @@ @@ -16,4 +16,8 @@
background: none;
}
}
.super-emoji.active {
background-color: var(--primary-color) !important;
}
}

6
src/scss/partials/_chatlist.scss

@ -76,11 +76,11 @@ @@ -76,11 +76,11 @@
}
ul.chatlist {
padding: 0 .5rem .5rem;
padding: 0 .5rem/* .5rem */;
@include respond-to(handhelds) {
/* @include respond-to(handhelds) {
padding: 0 0 .5rem;
}
} */
}
.chatlist {

22
src/scss/partials/popups/_instanceDeactivated.scss

@ -0,0 +1,22 @@ @@ -0,0 +1,22 @@
.popup-instance-deactivated {
background-color: rgba(0, 0, 0, .6);
.instance-deactivated-container {
margin: auto;
text-align: center;
pointer-events: none;
}
.header {
font-size: 2rem;
color: #fff;
//line-height: var(--line-height);
}
.subtitle {
color: #fff;
opacity: .6;
font-size: 1.5rem;
line-height: var(--line-height);
}
}

29
src/scss/style.scss

@ -270,6 +270,7 @@ html.night { @@ -270,6 +270,7 @@ html.night {
@import "partials/popups/datePicker";
@import "partials/popups/createPoll";
@import "partials/popups/forward";
@import "partials/popups/instanceDeactivated";
@import "partials/pages/pages";
@import "partials/pages/authCode";
@ -419,6 +420,34 @@ body { @@ -419,6 +420,34 @@ body {
color: var(--primary-text-color);
}
body.deactivated {
animation: grayscale-in var(--transition-standard-in) forwards;
}
body.deactivated-backwards {
animation: grayscale-out var(--transition-standard-out) forwards;
}
@keyframes grayscale-in {
0% {
filter: grayscale(0);
}
100% {
filter: grayscale(1);
}
}
@keyframes grayscale-out {
0% {
filter: grayscale(1);
}
100% {
filter: grayscale(0);
}
}
/* body {
position: absolute;
top: 0;

3
src/types.d.ts vendored

@ -81,5 +81,6 @@ export type ConnectionStatusChange = { @@ -81,5 +81,6 @@ export type ConnectionStatusChange = {
name: string,
isFileNetworker: boolean,
isFileDownload: boolean,
isFileUpload: boolean
isFileUpload: boolean,
timeout?: number
};

19
src/vendor/emoji/regex.ts vendored

File diff suppressed because one or more lines are too long
Loading…
Cancel
Save