946 lines
29 KiB
TypeScript
946 lines
29 KiB
TypeScript
import Config from './config';
|
||
|
||
import emojiRegExp from '../emoji/regex';
|
||
import { encodeEmoji } from '../emoji';
|
||
import { MOUNT_CLASS_TO } from './mtproto/mtproto_config';
|
||
import { MessageEntity } from '../layer';
|
||
import { copy } from '../helpers/object';
|
||
import { encodeEntities } from '../helpers/string';
|
||
import { isSafari } from '../helpers/userAgent';
|
||
|
||
const EmojiHelper = {
|
||
emojiMap: (code: string) => { return code; },
|
||
shortcuts: [] as any,
|
||
emojis: [] as any
|
||
};
|
||
|
||
const emojiData = Config.Emoji;
|
||
|
||
const alphaCharsRegExp = 'a-z' +
|
||
'\\u00c0-\\u00d6\\u00d8-\\u00f6\\u00f8-\\u00ff' + // Latin-1
|
||
'\\u0100-\\u024f' + // Latin Extended A and B
|
||
'\\u0253\\u0254\\u0256\\u0257\\u0259\\u025b\\u0263\\u0268\\u026f\\u0272\\u0289\\u028b' + // IPA Extensions
|
||
'\\u02bb' + // Hawaiian
|
||
'\\u0300-\\u036f' + // Combining diacritics
|
||
'\\u1e00-\\u1eff' + // Latin Extended Additional (mostly for Vietnamese)
|
||
'\\u0400-\\u04ff\\u0500-\\u0527' + // Cyrillic
|
||
'\\u2de0-\\u2dff\\ua640-\\ua69f' + // Cyrillic Extended A/B
|
||
'\\u0591-\\u05bf\\u05c1-\\u05c2\\u05c4-\\u05c5\\u05c7' +
|
||
'\\u05d0-\\u05ea\\u05f0-\\u05f4' + // Hebrew
|
||
'\\ufb1d-\\ufb28\\ufb2a-\\ufb36\\ufb38-\\ufb3c\\ufb3e\\ufb40-\\ufb41' +
|
||
'\\ufb43-\\ufb44\\ufb46-\\ufb4f' + // Hebrew Pres. Forms
|
||
'\\u0610-\\u061a\\u0620-\\u065f\\u066e-\\u06d3\\u06d5-\\u06dc' +
|
||
'\\u06de-\\u06e8\\u06ea-\\u06ef\\u06fa-\\u06fc\\u06ff' + // Arabic
|
||
'\\u0750-\\u077f\\u08a0\\u08a2-\\u08ac\\u08e4-\\u08fe' + // Arabic Supplement and Extended A
|
||
'\\ufb50-\\ufbb1\\ufbd3-\\ufd3d\\ufd50-\\ufd8f\\ufd92-\\ufdc7\\ufdf0-\\ufdfb' + // Pres. Forms A
|
||
'\\ufe70-\\ufe74\\ufe76-\\ufefc' + // Pres. Forms B
|
||
'\\u200c' + // Zero-Width Non-Joiner
|
||
'\\u0e01-\\u0e3a\\u0e40-\\u0e4e' + // Thai
|
||
'\\u1100-\\u11ff\\u3130-\\u3185\\uA960-\\uA97F\\uAC00-\\uD7AF\\uD7B0-\\uD7FF' + // Hangul (Korean)
|
||
'\\u3003\\u3005\\u303b' + // Kanji/Han iteration marks
|
||
'\\uff21-\\uff3a\\uff41-\\uff5a' + // full width Alphabet
|
||
'\\uff66-\\uff9f' + // half width Katakana
|
||
'\\uffa1-\\uffdc'; // half width Hangul (Korean)
|
||
const alphaNumericRegExp = '0-9\_' + alphaCharsRegExp;
|
||
const domainAddChars = '\u00b7';
|
||
// Based on Regular Expression for URL validation by Diego Perini
|
||
const urlRegExp = '((?:https?|ftp)://|mailto:)?' +
|
||
// user:pass authentication
|
||
'(?:\\S{1,64}(?::\\S{0,64})?@)?' +
|
||
'(?:' +
|
||
// sindresorhus/ip-regexp
|
||
'(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])(?:\\.(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])){3}' +
|
||
'|' +
|
||
// host name
|
||
'[' + alphaCharsRegExp + '0-9][' + alphaCharsRegExp + domainAddChars + '0-9\-]{0,64}' +
|
||
// domain name
|
||
'(?:\\.[' + alphaCharsRegExp + '0-9][' + alphaCharsRegExp + domainAddChars + '0-9\-]{0,64}){0,10}' +
|
||
// TLD identifier
|
||
'(?:\\.(xn--[0-9a-z]{2,16}|[' + alphaCharsRegExp + ']{2,24}))' +
|
||
')' +
|
||
// port number
|
||
'(?::\\d{2,5})?' +
|
||
// resource path
|
||
'(?:/(?:\\S{0,255}[^\\s.;,(\\[\\]{}<>"\'])?)?';
|
||
const usernameRegExp = '[a-zA-Z\\d_]{5,32}';
|
||
const botCommandRegExp = '\\/([a-zA-Z\\d_]{1,32})(?:@(' + usernameRegExp + '))?(\\b|$)';
|
||
const fullRegExp = new RegExp('(^| )(@)(' + usernameRegExp + ')|(' + urlRegExp + ')|(\\n)|(' + emojiRegExp + ')|(^|[\\s\\(\\]])(#[' + alphaNumericRegExp + ']{2,64})|(^|\\s)' + botCommandRegExp, 'i')
|
||
const emailRegExp = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||
//const markdownTestRegExp = /[`_*@~]/;
|
||
const markdownRegExp = /(^|\s|\n)(````?)([\s\S]+?)(````?)([\s\n\.,:?!;]|$)|(^|\s|\x01)(`|~~|\*\*|__|_-_)([^\n]+?)\7([\x01\s\.,:?!;]|$)|@(\d+)\s*\((.+?)\)|(\[(.+?)\]\((.+?)\))/m;
|
||
const siteHashtags: {[siteName: string]: string} = {
|
||
Telegram: 'tg://search_hashtag?hashtag={1}',
|
||
Twitter: 'https://twitter.com/hashtag/{1}',
|
||
Instagram: 'https://instagram.com/explore/tags/{1}/',
|
||
'Google Plus': 'https://plus.google.com/explore/{1}'
|
||
};
|
||
|
||
const siteMentions: {[siteName: string]: string} = {
|
||
Telegram: '#/im?p=%40{1}',
|
||
Twitter: 'https://twitter.com/{1}',
|
||
Instagram: 'https://instagram.com/{1}/',
|
||
GitHub: 'https://github.com/{1}'
|
||
};
|
||
const markdownEntities: {[markdown: string]: any} = {
|
||
'`': 'messageEntityCode',
|
||
'``': 'messageEntityPre',
|
||
'**': 'messageEntityBold',
|
||
'__': 'messageEntityItalic',
|
||
'~~': 'messageEntityStrike',
|
||
'_-_': 'messageEntityUnderline'
|
||
};
|
||
|
||
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)/* .replace(/(-fe0f|fe0f)/g, '') */;
|
||
|
||
if(unified == '1f441-200d-1f5e8') {
|
||
unified = '1f441-fe0f-200d-1f5e8-fe0f';
|
||
}
|
||
|
||
if(!emojiData.hasOwnProperty(unified)/* && !emojiData.hasOwnProperty(unified.replace(/(-fe0f|fe0f)/g, '')) */) {
|
||
//if(!emojiData.hasOwnProperty(emojiCode) && !emojiData.hasOwnProperty(emojiCode.replace(/[\ufe0f\u200d]/g, ''))) {
|
||
//console.error('lol', unified);
|
||
return null;
|
||
}
|
||
|
||
return unified.replace(/(-fe0f|fe0f)/g, '');
|
||
}
|
||
|
||
export function parseEntities(text: string) {
|
||
var match;
|
||
var raw = text, url;
|
||
const entities: MessageEntity[] = [];
|
||
let matchIndex;
|
||
var rawOffset = 0;
|
||
// var start = tsNow()
|
||
while((match = raw.match(fullRegExp))) {
|
||
matchIndex = rawOffset + match.index;
|
||
|
||
//console.log('parseEntities match:', match);
|
||
|
||
if(match[3]) { // mentions
|
||
entities.push({
|
||
_: 'messageEntityMention',
|
||
offset: matchIndex + match[1].length,
|
||
length: match[2].length + match[3].length
|
||
});
|
||
} else if(match[4]) {
|
||
if(emailRegExp.test(match[4])) { // email
|
||
entities.push({
|
||
_: 'messageEntityEmail',
|
||
offset: matchIndex,
|
||
length: match[4].length
|
||
});
|
||
} else {
|
||
var url: any = false;
|
||
var protocol = match[5];
|
||
var tld = match[6];
|
||
var excluded = '';
|
||
if(tld) { // URL
|
||
if(!protocol && (tld.substr(0, 4) === 'xn--' || Config.TLD.indexOf(tld.toLowerCase()) !== -1)) {
|
||
protocol = 'http://';
|
||
}
|
||
|
||
if(protocol) {
|
||
var balanced = checkBrackets(match[4]);
|
||
if (balanced.length !== match[4].length) {
|
||
excluded = match[4].substring(balanced.length);
|
||
match[4] = balanced;
|
||
}
|
||
|
||
url = (match[5] ? '' : protocol) + match[4];
|
||
}
|
||
} else { // IP address
|
||
url = (match[5] ? '' : 'http://') + match[4];
|
||
}
|
||
|
||
if (url) {
|
||
entities.push({
|
||
_: 'messageEntityUrl',
|
||
offset: matchIndex,
|
||
length: match[4].length
|
||
});
|
||
}
|
||
}
|
||
} else if(match[7]) { // New line
|
||
entities.push({
|
||
_: 'messageEntityLinebreak',
|
||
offset: matchIndex,
|
||
length: 1
|
||
});
|
||
} else if(match[8]) { // Emoji
|
||
//console.log('hit', match[8]);
|
||
let emojiCoords = getEmojiSpritesheetCoords(match[8]);
|
||
if(emojiCoords) {
|
||
entities.push({
|
||
_: 'messageEntityEmoji',
|
||
offset: matchIndex,
|
||
length: match[8].length,
|
||
unicode: emojiCoords
|
||
});
|
||
}
|
||
} else if(match[11]) { // Hashtag
|
||
entities.push({
|
||
_: 'messageEntityHashtag',
|
||
offset: matchIndex + (match[10] ? match[10].length : 0),
|
||
length: match[11].length
|
||
});
|
||
} else if(match[12]) { // Bot command
|
||
entities.push({
|
||
_: 'messageEntityBotCommand',
|
||
offset: matchIndex + (match[11] ? match[11].length : 0),
|
||
length: 1 + match[12].length + (match[13] ? 1 + match[13].length : 0)
|
||
});
|
||
}
|
||
|
||
raw = raw.substr(match.index + match[0].length);
|
||
rawOffset += match.index + match[0].length;
|
||
}
|
||
|
||
// if (entities.length) {
|
||
// console.log('parse entities', text, entities.slice())
|
||
// }
|
||
return entities;
|
||
}
|
||
|
||
/* export function parseEmojis(text: string) {
|
||
return text.replace(/:([a-z0-9\-\+\*_]+?):/gi, function (all, shortcut) {
|
||
var emojiCode = EmojiHelper.shortcuts[shortcut]
|
||
if (emojiCode !== undefined) {
|
||
return EmojiHelper.emojis[emojiCode][0]
|
||
}
|
||
return all
|
||
})
|
||
} */
|
||
|
||
export function parseMarkdown(text: string, entities: MessageEntity[], noTrim?: any): string {
|
||
/* if(!markdownTestRegExp.test(text)) {
|
||
return noTrim ? text : text.trim();
|
||
} */
|
||
|
||
let raw = text;
|
||
let match;
|
||
let newText: any = [];
|
||
let rawOffset = 0;
|
||
while(match = raw.match(markdownRegExp)) {
|
||
const matchIndex = rawOffset + match.index;
|
||
newText.push(raw.substr(0, match.index));
|
||
let text = (match[3] || match[8] || match[11] || match[14]);
|
||
rawOffset -= text.length;
|
||
//text = text.replace(/^\s+|\s+$/g, '');
|
||
rawOffset += text.length;
|
||
|
||
if(text.match(/^`*$/)) {
|
||
newText.push(match[0]);
|
||
} else if(match[3]) { // pre
|
||
if(match[5] == '\n') {
|
||
match[5] = '';
|
||
rawOffset -= 1;
|
||
}
|
||
|
||
newText.push(match[1] + text + match[5]);
|
||
entities.push({
|
||
_: 'messageEntityPre',
|
||
language: '',
|
||
offset: matchIndex + match[1].length,
|
||
length: text.length
|
||
});
|
||
|
||
rawOffset -= match[2].length + match[4].length;
|
||
} else if(match[7]) { // code|italic|bold
|
||
const isSOH = match[6] == '\x01';
|
||
if(!isSOH) {
|
||
newText.push(match[6] + text + match[9]);
|
||
} else {
|
||
newText.push(text);
|
||
}
|
||
|
||
entities.push({
|
||
_: markdownEntities[match[7]],
|
||
//offset: matchIndex + match[6].length,
|
||
offset: matchIndex + (isSOH ? 0 : match[6].length),
|
||
length: text.length
|
||
});
|
||
|
||
rawOffset -= match[7].length * 2 + (isSOH ? 2 : 0);
|
||
} else if(match[11]) { // custom mention
|
||
newText.push(text)
|
||
entities.push({
|
||
_: 'messageEntityMentionName',
|
||
user_id: +match[10],
|
||
offset: matchIndex,
|
||
length: text.length
|
||
});
|
||
|
||
rawOffset -= match[0].length - text.length;
|
||
} else if(match[12]) { // text url
|
||
newText.push(text);
|
||
entities.push({
|
||
_: 'messageEntityTextUrl',
|
||
url: match[13],
|
||
offset: matchIndex,
|
||
length: text.length
|
||
});
|
||
|
||
rawOffset -= match[12].length - text.length;
|
||
}
|
||
|
||
raw = raw.substr(match.index + match[0].length);
|
||
rawOffset += match.index + match[0].length;
|
||
}
|
||
|
||
newText.push(raw);
|
||
newText = newText.join('');
|
||
if(!newText.replace(/\s+/g, '').length) {
|
||
newText = text;
|
||
entities.splice(0, entities.length);
|
||
}
|
||
|
||
if(!entities.length && !noTrim) {
|
||
newText = newText.trim();
|
||
}
|
||
|
||
return newText;
|
||
}
|
||
|
||
export function mergeEntities(currentEntities: MessageEntity[], newEntities: MessageEntity[], fromApi?: boolean) {
|
||
const totalEntities = newEntities.slice();
|
||
const newLength = newEntities.length;
|
||
let startJ = 0;
|
||
for(let i = 0, length = currentEntities.length; i < length; i++) {
|
||
const curEntity = currentEntities[i];
|
||
/* if(fromApi &&
|
||
curEntity._ != 'messageEntityLinebreak' &&
|
||
curEntity._ != 'messageEntityEmoji') {
|
||
continue;
|
||
} */
|
||
// console.log('s', curEntity, newEntities);
|
||
const start = curEntity.offset;
|
||
const end = start + curEntity.length;
|
||
let bad = false;
|
||
for(let j = startJ; j < newLength; j++) {
|
||
const newEntity = newEntities[j];
|
||
const cStart = newEntity.offset;
|
||
const cEnd = cStart + newEntity.length;
|
||
if(cStart <= start) {
|
||
startJ = j;
|
||
}
|
||
|
||
if(start >= cStart && start < cEnd ||
|
||
end > cStart && end <= cEnd) {
|
||
// console.log('bad', curEntity, newEntity)
|
||
if(fromApi && start >= cStart && end <= cEnd) {
|
||
if(newEntity.nested === undefined) {
|
||
newEntity.nested = [];
|
||
}
|
||
|
||
curEntity.offset -= cStart;
|
||
newEntity.nested.push(copy(curEntity));
|
||
}
|
||
|
||
bad = true;
|
||
break;
|
||
}
|
||
|
||
if(cStart >= end) {
|
||
break;
|
||
}
|
||
}
|
||
|
||
if(bad) {
|
||
continue;
|
||
}
|
||
|
||
totalEntities.push(curEntity);
|
||
}
|
||
|
||
totalEntities.sort((a, b) => {
|
||
return a.offset - b.offset;
|
||
});
|
||
// console.log('merge', currentEntities, newEntities, totalEntities)
|
||
return totalEntities;
|
||
}
|
||
|
||
export function wrapRichNestedText(text: string, nested: MessageEntity[], options: any) {
|
||
if(nested === undefined) {
|
||
return encodeEntities(text);
|
||
}
|
||
|
||
options.hasNested = true;
|
||
return wrapRichText(text, {entities: nested, nested: true});
|
||
}
|
||
|
||
export function wrapRichText(text: string, options: Partial<{
|
||
entities: MessageEntity[],
|
||
contextSite: string,
|
||
highlightUsername: string,
|
||
noLinks: true,
|
||
noLinebreaks: true,
|
||
noCommands: true,
|
||
wrappingDraft: true,
|
||
fromBot: boolean,
|
||
noTextFormat: true,
|
||
passEntities: Partial<{
|
||
[_ in MessageEntity['_']]: true
|
||
}>,
|
||
|
||
nested?: true,
|
||
contextHashtag?: string
|
||
}> = {}) {
|
||
if(!text || !text.length) {
|
||
return '';
|
||
}
|
||
|
||
const passEntities: typeof options.passEntities = options.passEntities || {};
|
||
const entities = options.entities || parseEntities(text);
|
||
const contextSite = options.contextSite || 'Telegram';
|
||
const contextExternal = contextSite != 'Telegram';
|
||
|
||
//console.log('wrapRichText got entities:', text, entities);
|
||
const html: string[] = [];
|
||
let lastOffset = 0;
|
||
for(let i = 0, len = entities.length; i < len; i++) {
|
||
const entity = entities[i];
|
||
if(entity.offset > lastOffset) {
|
||
html.push(
|
||
encodeEntities(text.substr(lastOffset, entity.offset - lastOffset))
|
||
);
|
||
} else if(entity.offset < lastOffset) {
|
||
continue;
|
||
}
|
||
|
||
let skipEntity = false;
|
||
const entityText = text.substr(entity.offset, entity.length);
|
||
switch(entity._) {
|
||
case 'messageEntityMention':
|
||
var contextUrl = !options.noLinks && siteMentions[contextSite]
|
||
if (!contextUrl) {
|
||
skipEntity = true
|
||
break
|
||
}
|
||
var username = entityText.substr(1)
|
||
var attr = ''
|
||
if (options.highlightUsername &&
|
||
options.highlightUsername.toLowerCase() == username.toLowerCase()) {
|
||
attr = 'class="im_message_mymention"'
|
||
}
|
||
html.push(
|
||
'<a ',
|
||
attr,
|
||
contextExternal ? ' target="_blank" rel="noopener noreferrer" ' : '',
|
||
' href="',
|
||
contextUrl.replace('{1}', encodeURIComponent(username)),
|
||
'">',
|
||
wrapRichNestedText(entityText, entity.nested, options),
|
||
//encodeEntities(entityText),
|
||
'</a>'
|
||
)
|
||
break;
|
||
|
||
case 'messageEntityMentionName':
|
||
if(options.noLinks) {
|
||
skipEntity = true;
|
||
break;
|
||
}
|
||
|
||
html.push(
|
||
'<a href="#/im?p=u',
|
||
encodeURIComponent(entity.user_id),
|
||
'">',
|
||
wrapRichNestedText(entityText, entity.nested, options),
|
||
'</a>'
|
||
);
|
||
break;
|
||
|
||
case 'messageEntityHashtag':
|
||
var contextUrl = !options.noLinks && siteHashtags[contextSite];
|
||
if(!contextUrl) {
|
||
skipEntity = true;
|
||
break;
|
||
}
|
||
|
||
var hashtag = entityText.substr(1);
|
||
html.push(
|
||
'<a ',
|
||
contextExternal ? ' target="_blank" rel="noopener noreferrer" ' : '',
|
||
'href="',
|
||
contextUrl.replace('{1}', encodeURIComponent(hashtag))
|
||
,
|
||
'">',
|
||
encodeEntities(entityText),
|
||
'</a>'
|
||
);
|
||
break;
|
||
|
||
case 'messageEntityEmail':
|
||
if(options.noLinks) {
|
||
skipEntity = true;
|
||
break;
|
||
}
|
||
|
||
html.push(
|
||
'<a href="',
|
||
encodeEntities('mailto:' + entityText),
|
||
'" target="_blank" rel="noopener noreferrer">',
|
||
encodeEntities(entityText),
|
||
'</a>'
|
||
);
|
||
break;
|
||
|
||
case 'messageEntityUrl':
|
||
case 'messageEntityTextUrl':
|
||
let inner: string;
|
||
let url: string;
|
||
if(entity._ == 'messageEntityTextUrl') {
|
||
url = (entity as MessageEntity.messageEntityTextUrl).url;
|
||
url = wrapUrl(url, true);
|
||
inner = wrapRichNestedText(entityText, entity.nested, options);
|
||
} else {
|
||
url = wrapUrl(entityText, false);
|
||
inner = encodeEntities(replaceUrlEncodings(entityText));
|
||
}
|
||
|
||
if(options.noLinks && !passEntities[entity._]) {
|
||
html.push(inner);
|
||
} else {
|
||
html.push(
|
||
'<a href="',
|
||
encodeEntities(url),
|
||
'" target="_blank" rel="noopener noreferrer">',
|
||
inner,
|
||
'</a>'
|
||
);
|
||
}
|
||
break;
|
||
|
||
case 'messageEntityLinebreak':
|
||
html.push(options.noLinebreaks ? ' ' : '<br/>');
|
||
break;
|
||
|
||
case 'messageEntityEmoji':
|
||
if(options.wrappingDraft && emojiSupported) { // * fix safari emoji
|
||
html.push(encodeEntities(entityText));
|
||
break;
|
||
}
|
||
|
||
html.push(emojiSupported ? // ! contenteditable="false" нужен для поля ввода, иначе там будет меняться шрифт в Safari, или же рендерить смайлик напрямую, без контейнера
|
||
`<span class="emoji">${encodeEntities(entityText)}</span>` :
|
||
`<img src="assets/img/emoji/${entity.unicode}.png" alt="${encodeEntities(entityText)}" class="emoji">`);
|
||
break;
|
||
|
||
case 'messageEntityBotCommand':
|
||
if(options.noLinks || options.noCommands || contextExternal) {
|
||
skipEntity = true;
|
||
break;
|
||
}
|
||
|
||
var command = entityText.substr(1);
|
||
var bot;
|
||
var atPos;
|
||
if ((atPos = command.indexOf('@')) != -1) {
|
||
bot = command.substr(atPos + 1);
|
||
command = command.substr(0, atPos);
|
||
} else {
|
||
bot = options.fromBot;
|
||
}
|
||
|
||
html.push(
|
||
'<a href="',
|
||
encodeEntities('tg://bot_command?command=' + encodeURIComponent(command) + (bot ? '&bot=' + encodeURIComponent(bot) : '')),
|
||
'">',
|
||
encodeEntities(entityText),
|
||
'</a>'
|
||
);
|
||
break;
|
||
|
||
case 'messageEntityBold': {
|
||
if(options.noTextFormat) {
|
||
html.push(wrapRichNestedText(entityText, entity.nested, options));
|
||
break;
|
||
}
|
||
|
||
if(options.wrappingDraft) {
|
||
html.push(`<span style="font-weight: bold;">${wrapRichNestedText(entityText, entity.nested, options)}</span>`);
|
||
} else {
|
||
html.push(`<strong>${wrapRichNestedText(entityText, entity.nested, options)}</strong>`);
|
||
}
|
||
break;
|
||
}
|
||
|
||
case 'messageEntityItalic': {
|
||
if(options.noTextFormat) {
|
||
html.push(wrapRichNestedText(entityText, entity.nested, options));
|
||
break;
|
||
}
|
||
|
||
if(options.wrappingDraft) {
|
||
html.push(`<span style="font-style: italic;">${wrapRichNestedText(entityText, entity.nested, options)}</span>`);
|
||
} else {
|
||
html.push(`<em>${wrapRichNestedText(entityText, entity.nested, options)}</em>`);
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
case 'messageEntityHighlight':
|
||
html.push(
|
||
'<i>',
|
||
wrapRichNestedText(entityText, entity.nested, options),
|
||
'</i>'
|
||
);
|
||
break;
|
||
|
||
case 'messageEntityStrike':
|
||
if(options.wrappingDraft) {
|
||
const styleName = isSafari ? 'text-decoration' : 'text-decoration-line';
|
||
html.push(`<span style="${styleName}: line-through;">${wrapRichNestedText(entityText, entity.nested, options)}</span>`);
|
||
} else {
|
||
html.push(`<del>${wrapRichNestedText(entityText, entity.nested, options)}</del>`);
|
||
}
|
||
break;
|
||
|
||
case 'messageEntityUnderline':
|
||
if(options.wrappingDraft) {
|
||
const styleName = isSafari ? 'text-decoration' : 'text-decoration-line';
|
||
html.push(`<span style="${styleName}: underline;">${wrapRichNestedText(entityText, entity.nested, options)}</span>`);
|
||
} else {
|
||
html.push(`<u>${wrapRichNestedText(entityText, entity.nested, options)}</u>`);
|
||
}
|
||
break;
|
||
|
||
case 'messageEntityCode':
|
||
if(options.noTextFormat) {
|
||
html.push(encodeEntities(entityText));
|
||
break;
|
||
}
|
||
|
||
if(options.wrappingDraft) {
|
||
html.push(`<span style="font-family: monospace;">${encodeEntities(entityText)}</span>`);
|
||
} else {
|
||
html.push(
|
||
'<code>',
|
||
encodeEntities(entityText),
|
||
'</code>'
|
||
);
|
||
}
|
||
|
||
break;
|
||
|
||
case 'messageEntityPre':
|
||
if(options.noTextFormat) {
|
||
html.push(encodeEntities(entityText));
|
||
break;
|
||
}
|
||
|
||
html.push(
|
||
'<pre><code', (entity.language ? ' class="language-' + encodeEntities(entity.language) + '"' : ''), '>',
|
||
encodeEntities(entityText),
|
||
'</code></pre>'
|
||
);
|
||
break;
|
||
|
||
default:
|
||
skipEntity = true;
|
||
}
|
||
|
||
lastOffset = entity.offset + (skipEntity ? 0 : entity.length);
|
||
}
|
||
|
||
html.push(encodeEntities(text.substr(lastOffset))); // may be empty string
|
||
//console.log(html);
|
||
text = html.join('');
|
||
|
||
return text;
|
||
}
|
||
|
||
/* export function wrapDraftText(text: string, options: any = {}) {
|
||
if(!text || !text.length) {
|
||
return '';
|
||
}
|
||
|
||
var entities = options.entities;
|
||
if(entities === undefined) {
|
||
entities = parseEntities(text);
|
||
}
|
||
var i = 0;
|
||
var len = entities.length;
|
||
var entity;
|
||
var entityText;
|
||
var skipEntity;
|
||
var code = [];
|
||
var lastOffset = 0;
|
||
for(i = 0; i < len; i++) {
|
||
entity = entities[i];
|
||
if(entity.offset > lastOffset) {
|
||
code.push(
|
||
text.substr(lastOffset, entity.offset - lastOffset)
|
||
);
|
||
} else if(entity.offset < lastOffset) {
|
||
continue;
|
||
}
|
||
|
||
skipEntity = false;
|
||
entityText = text.substr(entity.offset, entity.length);
|
||
switch(entity._) {
|
||
case 'messageEntityEmoji':
|
||
code.push(
|
||
':',
|
||
entity.title,
|
||
':'
|
||
);
|
||
break;
|
||
|
||
case 'messageEntityCode':
|
||
code.push(
|
||
'`', entityText, '`'
|
||
);
|
||
break;
|
||
|
||
case 'messageEntityBold':
|
||
code.push(
|
||
'**', entityText, '**'
|
||
);
|
||
break;
|
||
|
||
case 'messageEntityItalic':
|
||
code.push(
|
||
'__', entityText, '__'
|
||
);
|
||
break;
|
||
|
||
case 'messageEntityPre':
|
||
code.push(
|
||
'```', entityText, '```'
|
||
);
|
||
break;
|
||
|
||
case 'messageEntityMentionName':
|
||
code.push(
|
||
'@', entity.user_id, ' (', entityText, ')'
|
||
);
|
||
break;
|
||
|
||
default:
|
||
skipEntity = true;
|
||
}
|
||
lastOffset = entity.offset + (skipEntity ? 0 : entity.length);
|
||
}
|
||
code.push(text.substr(lastOffset));
|
||
return code.join('');
|
||
} */
|
||
|
||
export function wrapDraftText(text: string, options: Partial<{
|
||
entities: MessageEntity[]
|
||
}> = {}) {
|
||
if(!text) {
|
||
return '';
|
||
}
|
||
|
||
let entities = (options.entities || []).slice();
|
||
|
||
return wrapRichText(text, {
|
||
entities,
|
||
noLinks: true,
|
||
wrappingDraft: true,
|
||
passEntities: {
|
||
messageEntityTextUrl: true
|
||
}
|
||
});
|
||
}
|
||
|
||
//const draftEntityTypes: MessageEntity['_'][] = (['messageEntityTextUrl', 'messageEntityEmoji'] as MessageEntity['_'][]).concat(Object.values(markdownEntities) as any);
|
||
/* const draftEntityTypes: Partial<{[_ in MessageEntity['_']]: true}> = {
|
||
messageEntityCode: true,
|
||
messageEntityPre: true,
|
||
messageEntityBold: true,
|
||
messageEntityItalic: true,
|
||
messageEntityStrike: true,
|
||
messageEntityEmoji: true,
|
||
messageEntityLinebreak: true,
|
||
messageEntityUnderline: true,
|
||
messageEntityTextUrl: true
|
||
};
|
||
export function wrapDraftText(text: string, options: Partial<{
|
||
entities: MessageEntity[]
|
||
}> = {}) {
|
||
const checkEntity = (entity: MessageEntity) => {
|
||
return draftEntityTypes[entity._];
|
||
};
|
||
const entities = options.entities ? options.entities.filter(entity => {
|
||
return draftEntityTypes[entity._];
|
||
}) : [];
|
||
|
||
return wrapRichText(text, {entities});
|
||
} */
|
||
|
||
export function checkBrackets(url: string) {
|
||
var urlLength = url.length;
|
||
var urlOpenBrackets = url.split('(').length - 1;
|
||
var urlCloseBrackets = url.split(')').length - 1;
|
||
while(urlCloseBrackets > urlOpenBrackets &&
|
||
url.charAt(urlLength - 1) === ')') {
|
||
url = url.substr(0, urlLength - 1)
|
||
urlCloseBrackets--;
|
||
urlLength--;
|
||
}
|
||
if(urlOpenBrackets > urlCloseBrackets) {
|
||
url = url.replace(/\)+$/, '');
|
||
}
|
||
return url;
|
||
}
|
||
|
||
export function replaceUrlEncodings(urlWithEncoded: string) {
|
||
return urlWithEncoded.replace(/(%[A-Z\d]{2})+/g, (str) => {
|
||
try {
|
||
return decodeURIComponent(str);
|
||
} catch (e) {
|
||
return str;
|
||
}
|
||
});
|
||
}
|
||
|
||
export function wrapPlainText(text: any) {
|
||
if(emojiSupported) {
|
||
return text;
|
||
}
|
||
|
||
if(!text || !text.length) {
|
||
return '';
|
||
}
|
||
|
||
text = text.replace(/\ufe0f/g, '', text);
|
||
var match;
|
||
var raw = text;
|
||
var text: any = [],
|
||
emojiTitle;
|
||
while((match = raw.match(fullRegExp))) {
|
||
text.push(raw.substr(0, match.index))
|
||
if(match[8]) {
|
||
// @ts-ignore
|
||
const emojiCode = EmojiHelper.emojiMap[match[8]];
|
||
if(emojiCode &&
|
||
// @ts-ignore
|
||
(emojiTitle = emojiData[emojiCode][1][0])) {
|
||
text.push(':' + emojiTitle + ':');
|
||
} else {
|
||
text.push(match[0]);
|
||
}
|
||
} else {
|
||
text.push(match[0]);
|
||
}
|
||
|
||
raw = raw.substr(match.index + match[0].length);
|
||
}
|
||
text.push(raw);
|
||
return text.join('');
|
||
}
|
||
|
||
export function wrapEmojiText(text: string) {
|
||
if(!text) return '';
|
||
|
||
let entities = parseEntities(text).filter(e => e._ == 'messageEntityEmoji');
|
||
return wrapRichText(text, {entities});
|
||
}
|
||
|
||
export function wrapUrl(url: string, unsafe?: number | boolean): string {
|
||
if(!url.match(/^https?:\/\//i)) {
|
||
url = 'https://' + url;
|
||
}
|
||
|
||
var tgMeMatch;
|
||
var telescoPeMatch;
|
||
/* if(unsafe == 2) {
|
||
url = 'tg://unsafe_url?url=' + encodeURIComponent(url);
|
||
} else */if((tgMeMatch = url.match(/^https?:\/\/t(?:elegram)?\.me\/(.+)/))) {
|
||
var fullPath = tgMeMatch[1];
|
||
var path = fullPath.split('/');
|
||
switch(path[0]) {
|
||
case 'joinchat':
|
||
url = 'tg://join?invite=' + path[1];
|
||
break;
|
||
|
||
case 'addstickers':
|
||
url = 'tg://addstickers?set=' + path[1];
|
||
break;
|
||
|
||
default:
|
||
if(path[1] && path[1].match(/^\d+$/)) {
|
||
url = 'tg://resolve?domain=' + path[0] + '&post=' + path[1];
|
||
} else if(path.length == 1) {
|
||
var domainQuery = path[0].split('?');
|
||
var domain = domainQuery[0];
|
||
var query = domainQuery[1];
|
||
if(domain == 'iv') {
|
||
var match = (query || '').match(/url=([^&=]+)/);
|
||
if(match) {
|
||
url = match[1];
|
||
try {
|
||
url = decodeURIComponent(url);
|
||
} catch (e) {}
|
||
|
||
return wrapUrl(url, unsafe);
|
||
}
|
||
}
|
||
|
||
url = 'tg://resolve?domain=' + domain + (query ? '&' + query : '');
|
||
}
|
||
}
|
||
} else if((telescoPeMatch = url.match(/^https?:\/\/telesco\.pe\/([^/?]+)\/(\d+)/))) {
|
||
url = 'tg://resolve?domain=' + telescoPeMatch[1] + '&post=' + telescoPeMatch[2];
|
||
}/* else if(unsafe) {
|
||
url = 'tg://unsafe_url?url=' + encodeURIComponent(url);
|
||
} */
|
||
|
||
return url;
|
||
}
|
||
|
||
export function matchUrl(text: string) {
|
||
return text.match(urlRegExp);
|
||
}
|
||
|
||
/* const el = document.createElement('span');
|
||
export function getAbbreviation(str: string, onlyFirst = false) {
|
||
const wrapped = wrapEmojiText(str);
|
||
el.innerHTML = wrapped;
|
||
|
||
const childNodes = el.childNodes;
|
||
let first = '', last = '';
|
||
|
||
const firstNode = childNodes[0];
|
||
if('length' in firstNode) first = (firstNode as any).textContent.trim().charAt(0).toUpperCase();
|
||
else first = (firstNode as HTMLElement).outerHTML;
|
||
|
||
if(onlyFirst) return first;
|
||
|
||
if(str.indexOf(' ') !== -1) {
|
||
const lastNode = childNodes[childNodes.length - 1];
|
||
if(lastNode == firstNode) last = lastNode.textContent.split(' ').pop().trim().charAt(0).toUpperCase();
|
||
else if('length' in lastNode) last = (lastNode as any).textContent.trim().charAt(0).toUpperCase();
|
||
else last = (lastNode as HTMLElement).outerHTML;
|
||
}
|
||
|
||
return first + last;
|
||
} */
|
||
export function getAbbreviation(str: string, onlyFirst = false) {
|
||
const splitted = str.trim().split(' ');
|
||
if(!splitted[0]) return '';
|
||
|
||
const first = [...splitted[0]][0];
|
||
|
||
if(onlyFirst || splitted.length == 1) return wrapEmojiText(first);
|
||
|
||
const last = [...splitted[splitted.length - 1]][0];
|
||
|
||
return wrapEmojiText(first + last);
|
||
}
|
||
}
|
||
|
||
MOUNT_CLASS_TO && (MOUNT_CLASS_TO.RichTextProcessor = RichTextProcessor);
|
||
|
||
export {RichTextProcessor};
|
||
export default RichTextProcessor;
|
||
|