Browse Source

[Firefox] fix choosing emoji

[Firefox] disable emoji dragging
[Firefox] fix opening privacy & security with uBlock
Save recent emoji on message send
Fix choosing loading emoji
master
Eduard Kuzmenko 3 years ago
parent
commit
d4c2f8bbb4
  1. 32
      src/components/chat/input.ts
  2. 110
      src/components/emoticonsDropdown/tabs/emoji.ts
  3. 2
      src/components/sidebarLeft/tabs/privacyAndSecurity.ts
  4. 3
      src/helpers/emojiSupport.ts
  5. 8
      src/helpers/userAgent.ts
  6. 12
      src/index.ts
  7. 2
      src/lib/appManagers/appEmojiManager.ts
  8. 3
      src/lib/richtextprocessor.ts
  9. 2
      src/lib/rootScope.ts
  10. 1
      src/scss/partials/_avatar.scss
  11. 2
      src/scss/partials/_leftSidebar.scss
  12. 1
      src/scss/style.scss
  13. 4
      src/vendor/emoji/index.ts

32
src/components/chat/input.ts

@ -43,7 +43,7 @@ import PopupPinMessage from '../popups/unpinMessage'; @@ -43,7 +43,7 @@ import PopupPinMessage from '../popups/unpinMessage';
import { debounce } from '../../helpers/schedulers';
import { tsNow } from '../../helpers/date';
import appNavigationController from '../appNavigationController';
import { isMobile } from '../../helpers/userAgent';
import { isMobile, isMobileSafari } from '../../helpers/userAgent';
import { i18n } from '../../lib/langPack';
import { generateTail } from './bubbles';
import findUpClassName from '../../helpers/dom/findUpClassName';
@ -64,6 +64,8 @@ import CommandsHelper from './commandsHelper'; @@ -64,6 +64,8 @@ import CommandsHelper from './commandsHelper';
import AutocompleteHelperController from './autocompleteHelperController';
import AutocompleteHelper from './autocompleteHelper';
import MentionsHelper from './mentionsHelper';
import fixSafariStickyInput from '../../helpers/dom/fixSafariStickyInput';
import { emojiFromCodePoints } from '../../vendor/emoji';
const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@ -1142,15 +1144,15 @@ export default class ChatInput { @@ -1142,15 +1144,15 @@ export default class ChatInput {
this.updateSendBtn();
};
public insertAtCaret(insertText: string, insertEntity?: MessageEntity) {
public insertAtCaret(insertText: string, insertEntity?: MessageEntity, isHelper = true) {
const {value: fullValue, caretPos, entities} = getRichValueWithCaret(this.messageInput);
const pos = caretPos >= 0 ? caretPos : fullValue.length;
const prefix = fullValue.substr(0, pos);
const suffix = fullValue.substr(pos);
const matches = prefix.match(ChatInput.AUTO_COMPLETE_REG_EXP);
const matches = isHelper ? prefix.match(ChatInput.AUTO_COMPLETE_REG_EXP) : null;
const matchIndex = matches.index + (matches[0].length - matches[2].length);
const matchIndex = matches ? matches.index + (matches[0].length - matches[2].length) : prefix.length;
const newPrefix = prefix.slice(0, matchIndex);
const newValue = newPrefix + insertText + suffix;
@ -1173,7 +1175,7 @@ export default class ChatInput { @@ -1173,7 +1175,7 @@ export default class ChatInput {
});
// add offset to entities next to emoji
const diff = insertLength - matches[2].length;
const diff = insertLength - (matches ? matches[2].length : prefix.length);
entities.forEach(entity => {
if(entity.offset >= matchIndex) {
entity.offset += diff;
@ -1423,7 +1425,17 @@ export default class ChatInput { @@ -1423,7 +1425,17 @@ export default class ChatInput {
};
public clearInput(canSetDraft = true) {
this.messageInputField.value = '';
if(document.activeElement === this.messageInput && isMobileSafari) {
const i = document.createElement('input');
document.body.append(i);
fixSafariStickyInput(i);
this.messageInputField.value = '';
fixSafariStickyInput(this.messageInput);
i.remove();
} else {
this.messageInputField.value = '';
}
if(isTouchSupported) {
//this.messageInput.innerText = '';
} else {
@ -1477,6 +1489,14 @@ export default class ChatInput { @@ -1477,6 +1489,14 @@ export default class ChatInput {
this.scheduleDate = undefined;
this.sendSilent = undefined;
const value = this.messageInputField.value;
const entities = RichTextProcessor.parseEntities(value);
const emojiEntities: MessageEntity.messageEntityEmoji[] = entities.filter(entity => entity._ === 'messageEntityEmoji') as any;
emojiEntities.forEach(entity => {
const emoji = emojiFromCodePoints(entity.unicode);
this.appEmojiManager.pushRecentEmoji(emoji);
});
if(clearInput) {
this.lastUrl = '';
delete this.noWebPage;

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

@ -15,6 +15,7 @@ import Config from "../../../lib/config"; @@ -15,6 +15,7 @@ import Config from "../../../lib/config";
import { i18n, LangPackKey } from "../../../lib/langPack";
import { RichTextProcessor } from "../../../lib/richtextprocessor";
import rootScope from "../../../lib/rootScope";
import { emojiFromCodePoints } from "../../../vendor/emoji";
import { putPreloader } from "../../misc";
import Scrollable from "../../scrollable";
import StickyIntersector from "../../stickyIntersector";
@ -53,10 +54,10 @@ export function appendEmoji(emoji: string, container: HTMLElement, prepend = fal @@ -53,10 +54,10 @@ export function appendEmoji(emoji: string, container: HTMLElement, prepend = fal
if(spanEmoji.firstElementChild && !RichTextProcessor.emojiSupported) {
const image = spanEmoji.firstElementChild as HTMLImageElement;
image.setAttribute('loading', 'lazy');
const url = image.src;
if(!loadedURLs.has(url)) {
image.setAttribute('loading', 'lazy');
const placeholder = document.createElement('span');
placeholder.classList.add('emoji-placeholder');
@ -89,6 +90,8 @@ export function appendEmoji(emoji: string, container: HTMLElement, prepend = fal @@ -89,6 +90,8 @@ export function appendEmoji(emoji: string, container: HTMLElement, prepend = fal
}
export function getEmojiFromElement(element: HTMLElement) {
if(!findUpClassName(element, 'super-emoji')) return '';
if(element.nodeType === 3) return element.nodeValue;
if(element.tagName === 'SPAN' && !element.classList.contains('emoji') && element.firstElementChild) {
element = element.firstElementChild as HTMLElement;
@ -170,7 +173,7 @@ export default class EmojiTab implements EmoticonsTab { @@ -170,7 +173,7 @@ export default class EmojiTab implements EmoticonsTab {
console.log('append emoji', emoji, emojiUnicode(emoji));
} */
let emoji = unified.split('-').reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
let emoji = emojiFromCodePoints(unified);
//if(emoji.includes('🕵')) {
//console.log('toCodePoints', toCodePoints(emoji));
//emoji = emoji.replace(/(\u200d[\u2640\u2642\u2695])(?!\ufe0f)/, '\ufe0f$1');
@ -236,64 +239,75 @@ export default class EmojiTab implements EmoticonsTab { @@ -236,64 +239,75 @@ export default class EmojiTab implements EmoticonsTab {
this.content.addEventListener('click', this.onContentClick);
this.stickyIntersector = EmoticonsDropdown.menuOnClick(menu, emojiScroll);
this.init = null;
}
onContentClick = (e: MouseEvent) => {
cancelEvent(e);
let target = e.target as HTMLElement;
//if(target.tagName !== 'SPAN') return;
rootScope.addEventListener('emoji_recent', (emoji) => {
const children = Array.from(this.recentItemsDiv.children) as HTMLElement[];
for(let i = 0, length = children.length; i < length; ++i) {
const el = children[i];
const _emoji = getEmojiFromElement(el);
if(emoji === _emoji) {
if(i === 0) {
return;
}
if(target.tagName === 'SPAN' && !target.classList.contains('emoji')) {
target = findUpClassName(target, 'super-emoji');
if(!target) {
return;
el.remove();
}
}
target = target.firstChild as HTMLElement;
} else if(target.tagName === 'DIV') return;
appendEmoji(emoji, this.recentItemsDiv, true);
this.recentItemsDiv.parentElement.classList.remove('hide');
});
}
// set selection range
const savedRange = isTouchSupported ? undefined : emoticonsDropdown.getSavedRange();
let sel: Selection;
if(savedRange) {
sel = document.getSelection();
sel.removeAllRanges();
sel.addRange(savedRange);
onContentClick = (e: MouseEvent) => {
cancelEvent(e);
const emoji = getEmojiFromElement(e.target as HTMLElement);
if(!emoji) {
return;
}
const html = RichTextProcessor.emojiSupported ?
(target.nodeType === 3 ? target.nodeValue : target.innerHTML) :
target.outerHTML;
const messageInput = appImManager.chat.input.messageInput;
let inputHTML = messageInput.innerHTML;
const html = RichTextProcessor.wrapEmojiText(emoji);
let inserted = false;
if(window.getSelection) {
const savedRange = isTouchSupported ? undefined : emoticonsDropdown.getSavedRange();
let sel = window.getSelection();
if(savedRange) {
sel.removeAllRanges();
sel.addRange(savedRange);
}
if((document.activeElement && (document.activeElement.tagName === 'INPUT' || document.activeElement.hasAttribute('contenteditable'))) ||
savedRange) {
document.execCommand('insertHTML', true, html);
} else {
appImManager.chat.input.messageInput.innerHTML += html;
if(sel.getRangeAt && sel.rangeCount) {
var el = document.createElement('div');
el.innerHTML = html;
var node = el.firstChild;
var range = sel.getRangeAt(0);
range.deleteContents();
//range.insertNode(document.createTextNode(' '));
range.insertNode(node);
range.setStart(node, 0);
inserted = true;
setTimeout(() => {
range = document.createRange();
range.setStartAfter(node);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}, 0);
}
}
/* if(sel && isTouchSupported) {
sel.removeRange(savedRange);
blurActiveElement();
} */
// Recent
const emoji = getEmojiFromElement(target);
(Array.from(this.recentItemsDiv.children) as HTMLElement[]).forEach((el, idx) => {
const _emoji = getEmojiFromElement(el);
if(emoji === _emoji) {
el.remove();
}
});
if(!inserted || messageInput.innerHTML === inputHTML) {
messageInput.insertAdjacentHTML('beforeend', html);
}
appendEmoji(emoji, this.recentItemsDiv, true);
appEmojiManager.pushRecentEmoji(emoji);
this.recentItemsDiv.parentElement.classList.remove('hide');
// Append to input
const event = new Event('input', {bubbles: true, cancelable: true});
appImManager.chat.input.messageInput.dispatchEvent(event);
messageInput.dispatchEvent(event);
};
onClose() {

2
src/components/sidebarLeft/tabs/privacyAndSecurity.ts

@ -34,7 +34,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable { @@ -34,7 +34,7 @@ export default class AppPrivacyAndSecurityTab extends SliderSuperTabEventable {
private authorizations: Authorization.authorization[];
protected async init() {
this.container.classList.add('privacy-container');
this.container.classList.add('dont-u-dare-block-me');
this.setTitle('PrivacySettings');
const SUBTITLE: LangPackKey = 'Loading';

3
src/helpers/emojiSupport.ts

@ -0,0 +1,3 @@ @@ -0,0 +1,3 @@
const IS_EMOJI_SUPPORTED = navigator.userAgent.search(/OS X|iPhone|iPad|iOS/i) !== -1/* && false *//* || true */;
export default IS_EMOJI_SUPPORTED;

8
src/helpers/userAgent.ts

@ -9,14 +9,6 @@ export const isApple = navigator.userAgent.search(/OS X|iPhone|iPad|iOS/i) !== - @@ -9,14 +9,6 @@ export const isApple = navigator.userAgent.search(/OS X|iPhone|iPad|iOS/i) !== -
export const isAndroid = navigator.userAgent.toLowerCase().indexOf('android') !== -1;
export const isChromium = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor);
/**
* Returns true when run in WebKit derived browsers.
* This is used as a workaround for a memory leak in Safari caused by using Transferable objects to
* transfer data between WebWorkers and the main thread.
* https://github.com/mapbox/mapbox-gl-js/issues/8771
*
* This should be removed once the underlying Safari issue is fixed.
*/
export const ctx = typeof(window) !== 'undefined' ? window : self;
// https://stackoverflow.com/a/58065241

12
src/index.ts

@ -6,9 +6,11 @@ @@ -6,9 +6,11 @@
import App from './config/app';
import blurActiveElement from './helpers/dom/blurActiveElement';
import { cancelEvent } from './helpers/dom/cancelEvent';
import findUpClassName from './helpers/dom/findUpClassName';
import fixSafariStickyInput from './helpers/dom/fixSafariStickyInput';
import loadFonts from './helpers/dom/loadFonts';
import IS_EMOJI_SUPPORTED from './helpers/emojiSupport';
import { isMobileSafari } from './helpers/userAgent';
import './materialize.scss';
import './scss/style.scss';
@ -142,6 +144,16 @@ console.timeEnd('get storage1'); */ @@ -142,6 +144,16 @@ console.timeEnd('get storage1'); */
toggleResizeMode();
});
if(userAgent.isFirefox && !IS_EMOJI_SUPPORTED) {
document.addEventListener('dragstart', (e) => {
const target = e.target as HTMLElement;
if(target.tagName === 'IMG' && target.classList.contains('emoji')) {
cancelEvent(e);
return false;
}
});
}
if(userAgent.isApple) {
if(userAgent.isSafari) {
document.documentElement.classList.add('is-safari');

2
src/lib/appManagers/appEmojiManager.ts

@ -10,6 +10,7 @@ import { validateInitObject } from "../../helpers/object"; @@ -10,6 +10,7 @@ import { validateInitObject } from "../../helpers/object";
import I18n from "../langPack";
import { isObject } from "../mtproto/bin_utils";
import apiManager from "../mtproto/mtprotoworker";
import rootScope from "../rootScope";
import SearchIndex from "../searchIndex";
import stateStorage from "../stateStorage";
import appStateManager from "./appStateManager";
@ -226,6 +227,7 @@ export class AppEmojiManager { @@ -226,6 +227,7 @@ export class AppEmojiManager {
}
appStateManager.pushToState('recentEmoji', recent);
rootScope.dispatchEvent('emoji_recent', emoji);
});
}
}

3
src/lib/richtextprocessor.ts

@ -17,6 +17,7 @@ import { MessageEntity } from '../layer'; @@ -17,6 +17,7 @@ import { MessageEntity } from '../layer';
import { encodeEntities } from '../helpers/string';
import { isSafari } from '../helpers/userAgent';
import { MOUNT_CLASS_TO } from '../config/debug';
import IS_EMOJI_SUPPORTED from '../helpers/emojiSupport';
const EmojiHelper = {
emojiMap: (code: string) => { return code; },
@ -114,7 +115,7 @@ for(let i in markdownEntities) { @@ -114,7 +115,7 @@ for(let i in markdownEntities) {
}
namespace RichTextProcessor {
export const emojiSupported = navigator.userAgent.search(/OS X|iPhone|iPad|iOS/i) !== -1/* && false *//* || true */;
export const emojiSupported = IS_EMOJI_SUPPORTED;
export function getEmojiSpritesheetCoords(emojiCode: string) {
let unified = encodeEmoji(emojiCode).replace(/-?fe0f/g, '');

2
src/lib/rootScope.ts

@ -123,6 +123,8 @@ export type BroadcastEvents = { @@ -123,6 +123,8 @@ export type BroadcastEvents = {
'push_init': PushSubscriptionNotify,
'push_subscribe': PushSubscriptionNotify,
'push_unsubscribe': PushSubscriptionNotify,
'emoji_recent': string
};
export class RootScope extends EventListenerBase<{

1
src/scss/partials/_avatar.scss

@ -87,6 +87,7 @@ avatar-element { @@ -87,6 +87,7 @@ avatar-element {
width: var(--size) !important;
height: var(--size) !important;
border-radius: inherit !important;
display: block; // fix Firefox below empty space
&.fade-in {
animation: fade-in-opacity .2s ease forwards;

2
src/scss/partials/_leftSidebar.scss

@ -1000,7 +1000,7 @@ @@ -1000,7 +1000,7 @@
}
}
.privacy-container {
.dont-u-dare-block-me {
.sidebar-left-section.no-delimiter {
padding-top: .75rem;
}

1
src/scss/style.scss

@ -1302,6 +1302,7 @@ middle-ellipsis-element { @@ -1302,6 +1302,7 @@ middle-ellipsis-element {
height: 1.75rem;
border-radius: 50%;
background-color: var(--light-secondary-text-color);
pointer-events: none;
@include animation-level(2) {
opacity: 0;

4
src/vendor/emoji/index.ts vendored

@ -38,4 +38,8 @@ export function toCodePoints(unicodeSurrogates: string): Array<string> { @@ -38,4 +38,8 @@ export function toCodePoints(unicodeSurrogates: string): Array<string> {
export function getEmojiToneIndex(input: string) {
let match = input.match(/[\uDFFB-\uDFFF]/);
return match ? 5 - (57343 - match[0].charCodeAt(0)) : 0;
}
export function emojiFromCodePoints(codePoints: string) {
return codePoints.split('-').reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
}
Loading…
Cancel
Save