Emoji parsing fixes
This commit is contained in:
parent
c002c43a08
commit
d9fb248fc3
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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
@ -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[]
|
||||||
}> = {}) {
|
}> = {}) {
|
||||||
|
156
src/scripts/emoji_compile_regex.js
Normal file
156
src/scripts/emoji_compile_regex.js
Normal 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);
|
27
src/scripts/format_emoji_regex.js
Normal file
27
src/scripts/format_emoji_regex.js
Normal file
File diff suppressed because one or more lines are too long
@ -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;
|
||||||
|
4879
src/scripts/in/emoji_test.txt
Normal file
4879
src/scripts/in/emoji_test.txt
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
1
src/scripts/out/emoji_regex.txt
Normal file
1
src/scripts/out/emoji_regex.txt
Normal file
File diff suppressed because one or more lines are too long
20
src/vendor/emoji/regex.ts
vendored
20
src/vendor/emoji/regex.ts
vendored
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user