Fix inserting emoji
This commit is contained in:
parent
a2cbf409af
commit
9f7c5377b5
@ -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[]) {
|
||||
|
@ -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);
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
33
src/helpers/dom/setCaretAt.ts
Normal file
33
src/helpers/dom/setCaretAt.ts
Normal file
@ -0,0 +1,33 @@
|
||||
/*
|
||||
* https://github.com/morethanwords/tweb
|
||||
* Copyright (C) 2019-2021 Eduard Kuzmenko
|
||||
* https://github.com/morethanwords/tweb/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -253,6 +253,7 @@
|
||||
padding: 0;
|
||||
height: 48px;
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.menu-horizontal-div-item {
|
||||
|
Loading…
Reference in New Issue
Block a user