Emoji parsing fixes

This commit is contained in:
morethanwords 2021-05-28 16:17:46 +03:00
parent c002c43a08
commit d9fb248fc3
13 changed files with 5141 additions and 45 deletions

View File

@ -27,6 +27,7 @@ import ListenerSetter from "../../helpers/listenerSetter";
import blurActiveElement from "../../helpers/dom/blurActiveElement"; import blurActiveElement from "../../helpers/dom/blurActiveElement";
import { attachClickEvent } from "../../helpers/dom/clickEvent"; import { attachClickEvent } from "../../helpers/dom/clickEvent";
import whichChild from "../../helpers/dom/whichChild"; import whichChild from "../../helpers/dom/whichChild";
import { cancelEvent } from "../../helpers/dom/cancelEvent";
export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown'; export const EMOTICONSSTICKERGROUP = 'emoticons-dropdown';
@ -158,7 +159,7 @@ export class EmoticonsDropdown {
}); });
this.deleteBtn = this.element.querySelector('.emoji-tabs-delete'); this.deleteBtn = this.element.querySelector('.emoji-tabs-delete');
this.deleteBtn.addEventListener('click', () => { this.deleteBtn.addEventListener('click', (e) => {
const input = appImManager.chat.input.messageInput; const input = appImManager.chat.input.messageInput;
if((input.lastChild as any)?.tagName) { if((input.lastChild as any)?.tagName) {
input.lastElementChild.remove(); input.lastElementChild.remove();
@ -173,6 +174,8 @@ export class EmoticonsDropdown {
const event = new Event('input', {bubbles: true, cancelable: true}); const event = new Event('input', {bubbles: true, cancelable: true});
appImManager.chat.input.messageInput.dispatchEvent(event); appImManager.chat.input.messageInput.dispatchEvent(event);
//appSidebarRight.stickersTab.init(); //appSidebarRight.stickersTab.init();
cancelEvent(e);
}); });
(this.tabsEl.children[1] as HTMLLIElement).click(); // set emoji tab (this.tabsEl.children[1] as HTMLLIElement).click(); // set emoji tab
@ -254,7 +257,7 @@ export class EmoticonsDropdown {
this.events.onOpen.forEach(cb => cb()); this.events.onOpen.forEach(cb => cb());
const sel = document.getSelection(); const sel = document.getSelection();
if(!sel.isCollapsed) { if(sel.rangeCount) {
this.savedRange = sel.getRangeAt(0); this.savedRange = sel.getRangeAt(0);
} }

View File

@ -5,8 +5,10 @@
*/ */
import emoticonsDropdown, { EmoticonsDropdown, EmoticonsTab } from ".."; import emoticonsDropdown, { EmoticonsDropdown, EmoticonsTab } from "..";
import { cancelEvent } from "../../../helpers/dom/cancelEvent";
import findUpClassName from "../../../helpers/dom/findUpClassName"; import findUpClassName from "../../../helpers/dom/findUpClassName";
import { fastRaf, pause } from "../../../helpers/schedulers"; import { fastRaf, pause } from "../../../helpers/schedulers";
import { isTouchSupported } from "../../../helpers/touchSupport";
import appEmojiManager from "../../../lib/appManagers/appEmojiManager"; import appEmojiManager from "../../../lib/appManagers/appEmojiManager";
import appImManager from "../../../lib/appManagers/appImManager"; import appImManager from "../../../lib/appManagers/appImManager";
import Config from "../../../lib/config"; import Config from "../../../lib/config";
@ -30,6 +32,7 @@ export function appendEmoji(emoji: string, container: HTMLElement, prepend = fal
if(unify) { if(unify) {
kek = RichTextProcessor.wrapSingleEmoji(emoji); kek = RichTextProcessor.wrapSingleEmoji(emoji);
} else { } else {
emoji = RichTextProcessor.fixEmoji(emoji);
kek = RichTextProcessor.wrapEmojiText(emoji); kek = RichTextProcessor.wrapEmojiText(emoji);
} }
@ -87,7 +90,7 @@ export function appendEmoji(emoji: string, container: HTMLElement, prepend = fal
export function getEmojiFromElement(element: HTMLElement) { export function getEmojiFromElement(element: HTMLElement) {
if(element.nodeType === 3) return element.nodeValue; if(element.nodeType === 3) return element.nodeValue;
if(element.tagName === 'SPAN' && !element.classList.contains('emoji')) { if(element.tagName === 'SPAN' && !element.classList.contains('emoji') && element.firstElementChild) {
element = element.firstElementChild as HTMLElement; element = element.firstElementChild as HTMLElement;
} }
@ -236,6 +239,7 @@ export default class EmojiTab implements EmoticonsTab {
} }
onContentClick = (e: MouseEvent) => { onContentClick = (e: MouseEvent) => {
cancelEvent(e);
let target = e.target as HTMLElement; let target = e.target as HTMLElement;
//if(target.tagName !== 'SPAN') return; //if(target.tagName !== 'SPAN') return;
@ -249,9 +253,10 @@ export default class EmojiTab implements EmoticonsTab {
} else if(target.tagName === 'DIV') return; } else if(target.tagName === 'DIV') return;
// set selection range // set selection range
const savedRange = emoticonsDropdown.getSavedRange(); const savedRange = isTouchSupported ? undefined : emoticonsDropdown.getSavedRange();
let sel: Selection;
if(savedRange) { if(savedRange) {
const sel = document.getSelection(); sel = document.getSelection();
sel.removeAllRanges(); sel.removeAllRanges();
sel.addRange(savedRange); sel.addRange(savedRange);
} }
@ -260,8 +265,17 @@ export default class EmojiTab implements EmoticonsTab {
(target.nodeType === 3 ? target.nodeValue : target.innerHTML) : (target.nodeType === 3 ? target.nodeValue : target.innerHTML) :
target.outerHTML; target.outerHTML;
// insert emoji in input if((document.activeElement && (document.activeElement.tagName === 'INPUT' || document.activeElement.hasAttribute('contenteditable'))) ||
document.execCommand('insertHTML', true, html); savedRange) {
document.execCommand('insertHTML', true, html);
} else {
appImManager.chat.input.messageInput.innerHTML += html;
}
/* if(sel && isTouchSupported) {
sel.removeRange(savedRange);
blurActiveElement();
} */
// Recent // Recent
const emoji = getEmojiFromElement(target); const emoji = getEmojiFromElement(target);

View File

@ -2403,9 +2403,11 @@ export class AppMessagesManager {
} */ } */
if(message.message && message.message.length && !message.totalEntities) { if(message.message && message.message.length && !message.totalEntities) {
const apiEntities = message.entities ? message.entities.slice() : [];
message.message = RichTextProcessor.fixEmoji(message.message, apiEntities);
const myEntities = RichTextProcessor.parseEntities(message.message); const myEntities = RichTextProcessor.parseEntities(message.message);
const apiEntities = message.entities || []; message.totalEntities = RichTextProcessor.mergeEntities(apiEntities, myEntities); // ! only in this order, otherwise bold and emoji formatting won't work
message.totalEntities = RichTextProcessor.mergeEntities(apiEntities.slice(), myEntities); // ! only in this order, otherwise bold and emoji formatting won't work
} }
storage[mid] = message; storage[mid] = message;

View File

@ -24,14 +24,14 @@ export class AppStickersManager {
private getStickersByEmoticonsPromises: {[emoticon: string]: Promise<Document[]>} = {}; private getStickersByEmoticonsPromises: {[emoticon: string]: Promise<Document[]>} = {};
constructor() { constructor() {
this.getStickerSet({id: 'emoji', access_hash: ''}, {overwrite: true}); this.getStickerSet({id: 'emoji', access_hash: ''});
rootScope.addMultipleEventsListeners({ rootScope.addMultipleEventsListeners({
updateNewStickerSet: (update) => { updateNewStickerSet: (update) => {
this.saveStickerSet(update.stickerset, update.stickerset.set.id); this.saveStickerSet(update.stickerset, update.stickerset.set.id);
rootScope.broadcast('stickers_installed', update.stickerset.set); rootScope.broadcast('stickers_installed', update.stickerset.set);
} }
}) });
} }
public saveStickers(docs: Document[]) { public saveStickers(docs: Document[]) {

File diff suppressed because one or more lines are too long

View File

@ -117,20 +117,21 @@ namespace RichTextProcessor {
export const emojiSupported = navigator.userAgent.search(/OS X|iPhone|iPad|iOS/i) !== -1/* && false *//* || true */; export const emojiSupported = navigator.userAgent.search(/OS X|iPhone|iPad|iOS/i) !== -1/* && false *//* || true */;
export function getEmojiSpritesheetCoords(emojiCode: string) { export function getEmojiSpritesheetCoords(emojiCode: string) {
let unified = encodeEmoji(emojiCode); let unified = encodeEmoji(emojiCode).replace(/-?fe0f/g, '');
if(unified === '1f441-200d-1f5e8') { /* if(unified === '1f441-200d-1f5e8') {
//unified = '1f441-fe0f-200d-1f5e8-fe0f'; //unified = '1f441-fe0f-200d-1f5e8-fe0f';
unified = '1f441-fe0f-200d-1f5e8'; unified = '1f441-fe0f-200d-1f5e8';
} } */
if(!emojiData.hasOwnProperty(unified) && !emojiData.hasOwnProperty(unified.replace(/-?fe0f$/, ''))/* && !emojiData.hasOwnProperty(unified.replace(/(-fe0f|fe0f)/g, '')) */) { if(!emojiData.hasOwnProperty(unified)
//if(!emojiData.hasOwnProperty(emojiCode) && !emojiData.hasOwnProperty(emojiCode.replace(/[\ufe0f\u200d]/g, ''))) { // && !emojiData.hasOwnProperty(unified.replace(/-?fe0f$/, ''))
) {
//console.error('lol', unified); //console.error('lol', unified);
return null; return null;
} }
return unified.replace(/-?fe0f/g, ''); return unified;
} }
export function parseEntities(text: string) { export function parseEntities(text: string) {
@ -530,12 +531,13 @@ namespace RichTextProcessor {
} }
case 'messageEntityEmoji': { case 'messageEntityEmoji': {
if(!(options.wrappingDraft && emojiSupported)) { // * fix safari emoji //if(!(options.wrappingDraft && emojiSupported)) { // * fix safari emoji
if(emojiSupported) { // ! contenteditable="false" нужен для поля ввода, иначе там будет меняться шрифт в Safari, или же рендерить смайлик напрямую, без контейнера if(!emojiSupported) { // no wrapping needed
insertPart(entity, '<span class="emoji">', '</span>'); // if(emojiSupported) { // ! contenteditable="false" нужен для поля ввода, иначе там будет меняться шрифт в Safari, или же рендерить смайлик напрямую, без контейнера
} else { // insertPart(entity, '<span class="emoji">', '</span>');
// } else {
insertPart(entity, `<img src="assets/img/emoji/${entity.unicode}.png" alt="`, `" class="emoji">`); insertPart(entity, `<img src="assets/img/emoji/${entity.unicode}.png" alt="`, `" class="emoji">`);
} // }
} }
/* if(!emojiSupported) { /* if(!emojiSupported) {
insertPart(entity, `<img src="assets/img/emoji/${entity.unicode}.png" alt="`, `" class="emoji">`); insertPart(entity, `<img src="assets/img/emoji/${entity.unicode}.png" alt="`, `" class="emoji">`);
@ -665,6 +667,34 @@ namespace RichTextProcessor {
return out; return out;
} }
export function fixEmoji(text: string, entities?: MessageEntity[]) {
/* if(!emojiSupported) {
return text;
} */
// '$`\ufe0f'
text = text.replace(/[\u2640\u2642\u2764](?!\ufe0f)/g, (match, offset, string) => {
if(entities) {
const length = match.length;
offset += length;
entities.forEach(entity => {
const end = entity.offset + entity.length;
if(end === offset) { // current entity
entity.length += length;
} else if(end > offset) {
entity.offset += length;
}
});
}
// console.log([match, offset, string]);
return match + '\ufe0f';
});
return text;
}
export function wrapDraftText(text: string, options: Partial<{ export function wrapDraftText(text: string, options: Partial<{
entities: MessageEntity[] entities: MessageEntity[]
}> = {}) { }> = {}) {

View File

@ -0,0 +1,156 @@
// @ts-check
const fs = require('fs');
const data = fs.readFileSync(__dirname + '/in/emoji_test.txt').toString();
/** @type {number[][]} */
const codepoints = [];
/** @type {Map<number, number[][]>} */
const codepointsByLength = new Map();
data.split('\n').forEach(line => {
if(!line || /^#/.test(line)) {
return;
}
const splitted = line.split(';');
if(splitted.length < 2 || !splitted[1].includes('fully-qualified')) {
return;
}
const a = String.fromCodePoint(...splitted[0].trim().split(' ').map((hex) => parseInt(hex, 16))).split('').map(str => str.charCodeAt(0));
codepoints.push(a);
let byLength = codepointsByLength.get(a.length);
if(!byLength) {
byLength = [];
codepointsByLength.set(a.length, byLength);
}
byLength.push(a);
});
/** @type {(codepoints: number[][]) => void} */
const sort = (codepoints) => {
codepoints.sort((a, b) => {
const length = Math.min(a.length, b.length);
for(let i = 0; i < length; ++i) {
const diff = a[i] - b[i];
if(diff) {
return diff;
}
}
return a.length - b.length;
});
};
sort(codepoints);
/** @type {(arr1: number[], arr2: number[]) => boolean} */
const isEqualArray = (arr1, arr2) => {
if(arr1.length !== arr2.length) {
return false;
}
for(let i = 0; i < arr1.length; ++i) {
if(arr1[i] !== arr2[i]) {
return false;
}
}
return true;
};
/** @type {(num: number) => string} */
const ttt = (num) => {
return '\\u' + num.toString(16);
};
/** @type {(arr: number[][], j: number) => string} */
const makeGroup = (arr, j) => {
let str = '';
if(arr.length > 1) str += '[';
str += arr.map(e => e.slice(0, j).map(ttt)).join('');
if(arr.length > 1) str += ']';
return str;
};
let str = '(?:';
let groups = [];
/* codepointsByLength.forEach((value) => {
sort(value);
value.forEach(s => {
str += s.reduce((acc, v) => acc + '\\u' + v.toString(16), '');
});
}); */
// for(let j = 1; j < 5; ++j) {
// for(let i = 0; i < codepoints.length; ++i) {
// const a = codepoints[i];
// /** @type {number[][]} */
// const set = [];
// //for(let j = 1; j < a.length; ++j) {
// const ending = a.slice(j);
// for(let k = i + 1; k < codepoints.length; ++k) {
// const b = codepoints[k];
// const e = b.slice(j);
// if(isEqualArray(ending, e)) {
// codepoints.splice(k, 1);
// set.push(b);
// }
// }
// //}
// if(set.length) {
// set.unshift(a);
// codepoints.splice(i, 1);
// console.log(set.length);
// } else if(j !== (5 - 1)) {
// continue;
// } else {
// set.push(a);
// }
// let group = makeGroup(set, j);
// group += ending.map(ttt).join('');
// groups.push(group);
// str += group;
// }
// }
/* codepointsByLength.forEach((codepoints) => {
for(let i = 0; i < codepoints.length; ++i) {
const a = codepoints[i];
}
}); */
for(let i = 0; i < codepoints.length; ++i) {
const a = codepoints[i];
for(let j = i + 1; j < codepoints.length; ++j) {
}
}
str += ')';
//console.log(codepointsByLength.get(1));
console.log(str);
/* let i = 0;
let s = [codepoints[i++][0]];
for(; i < codepoints.length; ++i) {
const c = codepoints[i];
if((c[0] - s[s.length - 1]) > 1) {
if(s.length > 1) {
console.log('start from', s);
}
s = [c[0]];
} else {
s.push(c[0]);
}
} */
//console.log(codepoints);

File diff suppressed because one or more lines are too long

View File

@ -153,8 +153,8 @@ if(false) {
.reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), ''); .reduce((prev, curr) => prev + String.fromCodePoint(parseInt(curr, 16)), '');
emoji = encodeEmoji(emoji); emoji = encodeEmoji(emoji);
//emoji = emoji.replace(/(-fe0f|fe0f)/g, ''); emoji = emoji.replace(/-?fe0f/g, '');
emoji = emoji.replace(/-?fe0f$/, ''); //emoji = emoji.replace(/-?fe0f$/, '');
let c = categories[category] === undefined ? 9 : categories[category]; let c = categories[category] === undefined ? 9 : categories[category];
//obj[emoji] = '' + c + sort_order; //obj[emoji] = '' + c + sort_order;

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long