Fix inserting emoji

This commit is contained in:
morethanwords 2021-11-11 02:58:04 +04:00
parent a2cbf409af
commit 9f7c5377b5
8 changed files with 133 additions and 74 deletions

View File

@ -60,7 +60,6 @@ import placeCaretAtEnd from '../../helpers/dom/placeCaretAtEnd';
import { MarkdownType, markdownTags } from '../../helpers/dom/getRichElementValue';
import getRichValueWithCaret from '../../helpers/dom/getRichValueWithCaret';
import EmojiHelper from './emojiHelper';
import setRichFocus from '../../helpers/dom/setRichFocus';
import CommandsHelper from './commandsHelper';
import AutocompleteHelperController from './autocompleteHelperController';
import AutocompleteHelper from './autocompleteHelper';
@ -82,7 +81,7 @@ import PopupPeer from '../popups/peer';
import MEDIA_MIME_TYPES_SUPPORTED from '../../environment/mediaMimeTypesSupport';
import appMediaPlaybackController from '../appMediaPlaybackController';
import { NULL_PEER_ID } from '../../lib/mtproto/mtproto_config';
import CheckboxField from '../checkboxField';
import setCaretAt from '../../helpers/dom/setCaretAt';
const RECORD_MIN_TIME = 500;
const POSTING_MEDIA_NOT_ALLOWED = 'Posting media content isn\'t allowed in this group.';
@ -1332,14 +1331,8 @@ export default class ChatInput {
insertEntity.offset = matchIndex;
}
addEntities.push({
_: 'messageEntityCaret',
length: 0,
offset: matchIndex + insertLength
});
// add offset to entities next to emoji
const diff = insertLength - (matches ? matches[2].length : prefix.length);
const diff = matches ? insertLength - matches[2].length : insertLength;
entities.forEach(entity => {
if(entity.offset >= matchIndex) {
entity.offset += diff;
@ -1348,13 +1341,34 @@ export default class ChatInput {
RichTextProcessor.mergeEntities(entities, addEntities);
if(/* caretPos !== -1 && caretPos !== fullValue.length */true) {
const caretEntity: MessageEntity.messageEntityCaret = {
_: 'messageEntityCaret',
offset: matchIndex + insertLength,
length: 0
};
let insertCaretAtIndex = 0;
for(let length = entities.length; insertCaretAtIndex < length; ++insertCaretAtIndex) {
const entity = entities[insertCaretAtIndex];
if(entity.offset > caretEntity.offset) {
break;
}
}
entities.splice(insertCaretAtIndex, 0, caretEntity);
}
//const saveExecuted = this.prepareDocumentExecute();
// can't exec .value here because it will instantly check for autocomplete
this.messageInputField.setValueSilently(RichTextProcessor.wrapDraftText(newValue, {entities}), true);
const value = RichTextProcessor.wrapDraftText(newValue, {entities});
this.messageInputField.setValueSilently(value, true);
const caret = this.messageInput.querySelector('.composer-sel');
setRichFocus(this.messageInput, caret);
caret.remove();
if(caret) {
setCaretAt(caret);
caret.remove();
}
// but it's needed to be checked only here
this.onMessageInput();
@ -1365,9 +1379,7 @@ export default class ChatInput {
}
public onEmojiSelected = (emoji: string, autocomplete: boolean) => {
if(autocomplete) {
this.insertAtCaret(emoji, RichTextProcessor.getEmojiEntityFromEmoji(emoji));
}
this.insertAtCaret(emoji, RichTextProcessor.getEmojiEntityFromEmoji(emoji), autocomplete);
};
private checkAutocomplete(value?: string, caretPos?: number, entities?: MessageEntity[]) {

View File

@ -27,6 +27,7 @@ import { cancelEvent } from "../../helpers/dom/cancelEvent";
import DropdownHover from "../../helpers/dropdownHover";
import { pause } from "../../helpers/schedulers/pause";
import appMessagesManager from "../../lib/appManagers/appMessagesManager";
import { IS_APPLE_MOBILE } from "../../environment/userAgent";
export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown';
@ -163,10 +164,18 @@ export class EmoticonsDropdown extends DropdownHover {
cancelEvent(e);
});
const HIDE_EMOJI_TAB = IS_APPLE_MOBILE;
(this.tabsEl.children[1] as HTMLLIElement).click(); // set emoji tab
if(this.tabs[0].init) {
this.tabs[0].init(); // onTransitionEnd не вызовется, т.к. это первая открытая вкладка
const INIT_TAB_ID = HIDE_EMOJI_TAB ? 1 : 0;
if(HIDE_EMOJI_TAB) {
(this.tabsEl.children[1] as HTMLElement).classList.add('hide');
}
(this.tabsEl.children[INIT_TAB_ID + 1] as HTMLLIElement).click(); // set emoji tab
if(this.tabs[INIT_TAB_ID].init) {
this.tabs[INIT_TAB_ID].init(); // onTransitionEnd не вызовется, т.к. это первая открытая вкладка
}
rootScope.addEventListener('peer_changed', this.checkRights);

View File

@ -9,7 +9,6 @@ import { cancelEvent } from "../../../helpers/dom/cancelEvent";
import findUpClassName from "../../../helpers/dom/findUpClassName";
import { fastRaf } from "../../../helpers/schedulers";
import { pause } from "../../../helpers/schedulers/pause";
import { IS_TOUCH_SUPPORTED } from "../../../environment/touchSupport";
import appEmojiManager from "../../../lib/appManagers/appEmojiManager";
import appImManager from "../../../lib/appManagers/appImManager";
import Config from "../../../lib/config";
@ -21,6 +20,8 @@ import { putPreloader } from "../../misc";
import Scrollable from "../../scrollable";
import StickyIntersector from "../../stickyIntersector";
import IS_EMOJI_SUPPORTED from "../../../environment/emojiSupport";
import { IS_TOUCH_SUPPORTED } from "../../../environment/touchSupport";
import blurActiveElement from "../../../helpers/dom/blurActiveElement";
const loadedURLs: Set<string> = new Set();
export function appendEmoji(emoji: string, container: HTMLElement, prepend = false, unify = false) {
@ -290,47 +291,10 @@ export default class EmojiTab implements EmoticonsTab {
return;
}
const messageInput = appImManager.chat.input.messageInput;
let inputHTML = messageInput.innerHTML;
const html = RichTextProcessor.wrapEmojiText(emoji, true);
let inserted = false;
if(window.getSelection) {
const savedRange = IS_TOUCH_SUPPORTED ? 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);
}
appImManager.chat.input.onEmojiSelected(emoji, false);
if(IS_TOUCH_SUPPORTED) {
blurActiveElement();
}
if(!inserted || messageInput.innerHTML === inputHTML) {
messageInput.insertAdjacentHTML('beforeend', html);
}
// Append to input
const event = new Event('input', {bubbles: true, cancelable: true});
messageInput.dispatchEvent(event);
};
onClose() {

View File

@ -22,11 +22,21 @@ export default function getRichValueWithCaret(field: HTMLElement, withEntities =
let selOffset: number;
if(sel && sel.rangeCount) {
const range = sel.getRangeAt(0);
if(range.startContainer &&
const startOffset = range.startOffset;
if(
range.startContainer &&
range.startContainer == range.endContainer &&
range.startOffset == range.endOffset) {
selNode = range.startContainer;
selOffset = range.startOffset;
startOffset == range.endOffset
) {
// * if focused on img
const possibleChildrenFocusOffset = startOffset - 1;
if(range.startContainer === field && field.childNodes[possibleChildrenFocusOffset]) {
selNode = field.childNodes[possibleChildrenFocusOffset];
selOffset = 1;
} else {
selNode = range.startContainer;
selOffset = startOffset;
}
}
}

View File

@ -0,0 +1,33 @@
/*
* https://github.com/morethanwords/tweb
* Copyright (C) 2019-2021 Eduard Kuzmenko
* https://github.com/morethanwords/tweb/blob/master/LICENSE
*/
export default function setCaretAt(node: Node) {
// node.appendChild(document.createTextNode(''));
const originalNode = node;
node = node.previousSibling;
if(node.nodeType === 1) {
const newNode = document.createTextNode('');
node.parentNode.insertBefore(newNode, !originalNode.nextSibling || originalNode.nextSibling.nodeType === node.nodeType ? originalNode : originalNode.nextSibling);
node = newNode;
}
if(window.getSelection && document.createRange) {
const range = document.createRange();
if(node) {
range.setStartAfter(node);
range.insertNode(node);
range.setStart(node, node.nodeValue.length);
}
range.collapse(true);
const sel = window.getSelection();
sel.removeAllRanges();
sel.addRange(range);
}
}

View File

@ -403,13 +403,14 @@ namespace RichTextProcessor {
currentEntities.push(...filtered);
currentEntities.sort((a, b) => a.offset - b.offset);
// currentEntities.sort((a, b) => (a.offset - b.offset) || (a._ === 'messageEntityCaret' && -1));
if(!IS_EMOJI_SUPPORTED) { // fix splitted emoji. messageEntityTextUrl can split the emoji if starts before its end (e.g. on fe0f)
for(let i = 0; i < currentEntities.length; ++i) {
const entity = currentEntities[i];
if(entity._ === 'messageEntityEmoji') {
const nextEntity = currentEntities[i + 1];
if(nextEntity && nextEntity.offset < (entity.offset + entity.length)) {
if(nextEntity /* && nextEntity._ !== 'messageEntityCaret' */ && nextEntity.offset < (entity.offset + entity.length)) {
entity.length = nextEntity.offset - entity.offset;
}
}
@ -460,7 +461,8 @@ namespace RichTextProcessor {
const lol: {
part: string,
offset: number
offset: number,
// priority: number
}[] = [];
const entities = options.entities || parseEntities(text);
@ -468,14 +470,16 @@ namespace RichTextProcessor {
const contextSite = options.contextSite || 'Telegram';
const contextExternal = contextSite !== 'Telegram';
const insertPart = (entity: MessageEntity, startPart: string, endPart?: string) => {
lol.push({part: startPart, offset: entity.offset});
const insertPart = (entity: MessageEntity, startPart: string, endPart?: string/* , priority = 0 */) => {
lol.push({part: startPart, offset: entity.offset/* , priority */});
if(endPart) {
lol.unshift({part: endPart, offset: entity.offset + entity.length});
lol.push({part: endPart, offset: entity.offset + entity.length/* , priority */});
}
};
const pushPartsAfterSort: typeof lol = [];
for(let i = 0, length = entities.length; i < length; ++i) {
const entity = entities[i];
switch(entity._) {
@ -579,9 +583,9 @@ namespace RichTextProcessor {
//} else if(options.mustWrapEmoji) {
} else if(!options.wrappingDraft) {
insertPart(entity, '<span class="emoji">', '</span>');
} else if(!IS_SAFARI) {
}/* else if(!IS_SAFARI) {
insertPart(entity, '<span class="emoji" contenteditable="false">', '</span>');
}
} */
/* if(!IS_EMOJI_SUPPORTED) {
insertPart(entity, `<img src="assets/img/emoji/${entity.unicode}.png" alt="`, `" class="emoji">`);
} */
@ -590,7 +594,12 @@ namespace RichTextProcessor {
}
case 'messageEntityCaret': {
insertPart(entity, '<span class="composer-sel"></span>');
const html = '<span class="composer-sel"></span>';
// const html = '<span class="composer-sel" contenteditable="false"></span>';
// insertPart(entity, '<span class="composer-sel" contenteditable="true"></span>');
// insertPart(entity, '<span class="composer-sel"></span>');
pushPartsAfterSort.push({part: html, offset: entity.offset});
// insertPart(entity, html/* , undefined, 1 */);
break;
}
@ -700,11 +709,28 @@ namespace RichTextProcessor {
}
}
lol.sort((a, b) => a.offset - b.offset);
// lol.sort((a, b) => (a.offset - b.offset) || (a.priority - b.priority));
lol.sort((a, b) => a.offset - b.offset); // have to sort because of nested entities
let partsLength = lol.length, partsAfterSortLength = pushPartsAfterSort.length;
for(let i = 0; i < partsAfterSortLength; ++i) {
const part = pushPartsAfterSort[i];
let insertAt = 0;
while(insertAt < partsLength) {
if(lol[insertAt++].offset >= part.offset) {
break;
}
}
lol.splice(insertAt, 0, part);
}
partsLength += partsAfterSortLength;
const arr: string[] = [];
let usedLength = 0;
for(const {part, offset} of lol) {
for(let i = 0; i < partsLength; ++i) {
const {part, offset} = lol[i];
if(offset > usedLength) {
arr.push(encodeEntities(text.slice(usedLength, offset)));
usedLength = offset;

View File

@ -159,8 +159,12 @@ $chat-helper-size: 36px;
content: $tgico-smile;
}
html.is-ios &:before {
content: $tgico-stickers;
}
&.flip-icon:before {
content: $tgico-keyboard;
content: $tgico-keyboard !important;
}
}

View File

@ -253,6 +253,7 @@
padding: 0;
height: 48px;
max-width: 100%;
position: relative;
}
.menu-horizontal-div-item {