[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
This commit is contained in:
parent
5ad39cf69d
commit
d4c2f8bbb4
@ -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';
|
||||
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 {
|
||||
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 {
|
||||
});
|
||||
|
||||
// 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 {
|
||||
};
|
||||
|
||||
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 {
|
||||
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;
|
||||
|
@ -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
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
this.content.addEventListener('click', this.onContentClick);
|
||||
this.stickyIntersector = EmoticonsDropdown.menuOnClick(menu, emojiScroll);
|
||||
this.init = null;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
el.remove();
|
||||
}
|
||||
}
|
||||
|
||||
appendEmoji(emoji, this.recentItemsDiv, true);
|
||||
this.recentItemsDiv.parentElement.classList.remove('hide');
|
||||
});
|
||||
}
|
||||
|
||||
onContentClick = (e: MouseEvent) => {
|
||||
cancelEvent(e);
|
||||
let target = e.target as HTMLElement;
|
||||
//if(target.tagName !== 'SPAN') return;
|
||||
|
||||
if(target.tagName === 'SPAN' && !target.classList.contains('emoji')) {
|
||||
target = findUpClassName(target, 'super-emoji');
|
||||
if(!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
target = target.firstChild as HTMLElement;
|
||||
} else if(target.tagName === 'DIV') return;
|
||||
|
||||
// set selection range
|
||||
const savedRange = isTouchSupported ? undefined : emoticonsDropdown.getSavedRange();
|
||||
let sel: Selection;
|
||||
if(savedRange) {
|
||||
sel = document.getSelection();
|
||||
sel.removeAllRanges();
|
||||
sel.addRange(savedRange);
|
||||
}
|
||||
|
||||
const html = RichTextProcessor.emojiSupported ?
|
||||
(target.nodeType === 3 ? target.nodeValue : target.innerHTML) :
|
||||
target.outerHTML;
|
||||
|
||||
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 && 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();
|
||||
}
|
||||
});
|
||||
|
||||
appendEmoji(emoji, this.recentItemsDiv, true);
|
||||
const emoji = getEmojiFromElement(e.target as HTMLElement);
|
||||
if(!emoji) {
|
||||
return;
|
||||
}
|
||||
|
||||
appEmojiManager.pushRecentEmoji(emoji);
|
||||
this.recentItemsDiv.parentElement.classList.remove('hide');
|
||||
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(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(!inserted || messageInput.innerHTML === inputHTML) {
|
||||
messageInput.insertAdjacentHTML('beforeend', html);
|
||||
}
|
||||
|
||||
// Append to input
|
||||
const event = new Event('input', {bubbles: true, cancelable: true});
|
||||
appImManager.chat.input.messageInput.dispatchEvent(event);
|
||||
messageInput.dispatchEvent(event);
|
||||
};
|
||||
|
||||
onClose() {
|
||||
|
@ -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
Normal file
3
src/helpers/emojiSupport.ts
Normal file
@ -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;
|
@ -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
12
src/index.ts
@ -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'); */
|
||||
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');
|
||||
|
@ -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 {
|
||||
}
|
||||
|
||||
appStateManager.pushToState('recentEmoji', recent);
|
||||
rootScope.dispatchEvent('emoji_recent', emoji);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
}
|
||||
|
||||
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, '');
|
||||
|
@ -123,6 +123,8 @@ export type BroadcastEvents = {
|
||||
'push_init': PushSubscriptionNotify,
|
||||
'push_subscribe': PushSubscriptionNotify,
|
||||
'push_unsubscribe': PushSubscriptionNotify,
|
||||
|
||||
'emoji_recent': string
|
||||
};
|
||||
|
||||
export class RootScope extends EventListenerBase<{
|
||||
|
@ -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;
|
||||
|
@ -1000,7 +1000,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
.privacy-container {
|
||||
.dont-u-dare-block-me {
|
||||
.sidebar-left-section.no-delimiter {
|
||||
padding-top: .75rem;
|
||||
}
|
||||
|
@ -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
4
src/vendor/emoji/index.ts
vendored
@ -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…
Reference in New Issue
Block a user