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

View File

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

View File

@ -2403,9 +2403,11 @@ export class AppMessagesManager {
} */
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 apiEntities = message.entities || [];
message.totalEntities = RichTextProcessor.mergeEntities(apiEntities.slice(), myEntities); // ! only in this order, otherwise bold and emoji formatting won't work
message.totalEntities = RichTextProcessor.mergeEntities(apiEntities, myEntities); // ! only in this order, otherwise bold and emoji formatting won't work
}
storage[mid] = message;

View File

@ -24,14 +24,14 @@ export class AppStickersManager {
private getStickersByEmoticonsPromises: {[emoticon: string]: Promise<Document[]>} = {};
constructor() {
this.getStickerSet({id: 'emoji', access_hash: ''}, {overwrite: true});
this.getStickerSet({id: 'emoji', access_hash: ''});
rootScope.addMultipleEventsListeners({
updateNewStickerSet: (update) => {
this.saveStickerSet(update.stickerset, update.stickerset.set.id);
rootScope.broadcast('stickers_installed', update.stickerset.set);
}
})
});
}
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 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';
}
} */
if(!emojiData.hasOwnProperty(unified) && !emojiData.hasOwnProperty(unified.replace(/-?fe0f$/, ''))/* && !emojiData.hasOwnProperty(unified.replace(/(-fe0f|fe0f)/g, '')) */) {
//if(!emojiData.hasOwnProperty(emojiCode) && !emojiData.hasOwnProperty(emojiCode.replace(/[\ufe0f\u200d]/g, ''))) {
if(!emojiData.hasOwnProperty(unified)
// && !emojiData.hasOwnProperty(unified.replace(/-?fe0f$/, ''))
) {
//console.error('lol', unified);
return null;
}
return unified.replace(/-?fe0f/g, '');
return unified;
}
export function parseEntities(text: string) {
@ -530,12 +531,13 @@ namespace RichTextProcessor {
}
case 'messageEntityEmoji': {
if(!(options.wrappingDraft && emojiSupported)) { // * fix safari emoji
if(emojiSupported) { // ! contenteditable="false" нужен для поля ввода, иначе там будет меняться шрифт в Safari, или же рендерить смайлик напрямую, без контейнера
insertPart(entity, '<span class="emoji">', '</span>');
} else {
//if(!(options.wrappingDraft && emojiSupported)) { // * fix safari emoji
if(!emojiSupported) { // no wrapping needed
// if(emojiSupported) { // ! contenteditable="false" нужен для поля ввода, иначе там будет меняться шрифт в Safari, или же рендерить смайлик напрямую, без контейнера
// insertPart(entity, '<span class="emoji">', '</span>');
// } else {
insertPart(entity, `<img src="assets/img/emoji/${entity.unicode}.png" alt="`, `" class="emoji">`);
}
// }
}
/* if(!emojiSupported) {
insertPart(entity, `<img src="assets/img/emoji/${entity.unicode}.png" alt="`, `" class="emoji">`);
@ -665,6 +667,34 @@ namespace RichTextProcessor {
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<{
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)), '');
emoji = encodeEmoji(emoji);
//emoji = emoji.replace(/(-fe0f|fe0f)/g, '');
emoji = emoji.replace(/-?fe0f$/, '');
emoji = emoji.replace(/-?fe0f/g, '');
//emoji = emoji.replace(/-?fe0f$/, '');
let c = categories[category] === undefined ? 9 : categories[category];
//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