2020-06-13 11:19:39 +03:00
import Config from './config' ;
2020-08-29 18:10:04 +03:00
import emojiRegExp from '../emoji/regex' ;
import { encodeEmoji } from '../emoji' ;
2020-09-29 05:03:26 +03:00
import { MOUNT_CLASS_TO } from './mtproto/mtproto_config' ;
2020-10-16 23:03:30 +03:00
import { MessageEntity } from '../layer' ;
2020-11-07 05:48:07 +02:00
import { copy } from '../helpers/object' ;
import { encodeEntities } from '../helpers/string' ;
2020-08-29 18:10:04 +03:00
2020-10-16 23:03:30 +03:00
const EmojiHelper = {
2020-08-29 18:10:04 +03:00
emojiMap : ( code : string ) = > { return code ; } ,
shortcuts : [ ] as any ,
emojis : [ ] as any
2020-06-13 11:19:39 +03:00
} ;
2020-10-16 23:03:30 +03:00
const emojiData = Config . Emoji ;
2020-06-13 11:19:39 +03:00
2020-10-16 23:03:30 +03:00
const alphaCharsRegExp = 'a-z' +
2020-06-13 11:19:39 +03:00
'\\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)
2020-10-16 23:03:30 +03:00
const alphaNumericRegExp = '0-9\_' + alphaCharsRegExp ;
const domainAddChars = '\u00b7' ;
2020-06-13 11:19:39 +03:00
// Based on Regular Expression for URL validation by Diego Perini
2020-10-16 23:03:30 +03:00
const urlRegExp = '((?:https?|ftp)://|mailto:)?' +
2020-06-13 11:19:39 +03:00
// 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
2020-08-29 18:10:04 +03:00
'(?:/(?:\\S{0,255}[^\\s.;,(\\[\\]{}<>"\'])?)?' ;
2020-10-16 23:03:30 +03:00
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,}))$/ ;
2020-11-14 04:27:13 +02:00
//const markdownTestRegExp = /[`_*@~]/;
const markdownRegExp = /(^|\s|\n)(````?)([\s\S]+?)(````?)([\s\n\.,:?!;]|$)|(^|\s)(`|~~|\*\*|__)([^\n]+?)\7([\s\.,:?!;]|$)|@(\d+)\s*\((.+?)\)|(\[(.+?)\]\((.+?)\))/m ;
2020-10-16 23:03:30 +03:00
const siteHashtags : { [ siteName : string ] : string } = {
2020-06-13 11:19:39 +03:00
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}'
2020-08-29 18:10:04 +03:00
} ;
2020-10-16 23:03:30 +03:00
const siteMentions : { [ siteName : string ] : string } = {
2020-06-13 11:19:39 +03:00
Telegram : '#/im?p=%40{1}' ,
Twitter : 'https://twitter.com/{1}' ,
Instagram : 'https://instagram.com/{1}/' ,
GitHub : 'https://github.com/{1}'
2020-08-29 18:10:04 +03:00
} ;
2020-10-16 23:03:30 +03:00
const markdownEntities = {
2020-06-13 11:19:39 +03:00
'`' : 'messageEntityCode' ,
2020-11-14 04:27:13 +02:00
'``' : 'messageEntityPre' ,
2020-06-13 11:19:39 +03:00
'**' : 'messageEntityBold' ,
2020-11-14 04:27:13 +02:00
'__' : 'messageEntityItalic' ,
'~~' : 'messageEntityStrike'
2020-08-29 18:10:04 +03:00
} ;
2020-06-13 11:19:39 +03:00
2020-10-16 23:03:30 +03:00
namespace RichTextProcessor {
export const emojiSupported = navigator . userAgent . search ( /OS X|iPhone|iPad|iOS/i ) != - 1 /* && false */ /* || true */ ;
2020-09-01 22:15:50 +03:00
2020-10-16 23:03:30 +03:00
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 , '' ) ;
2020-09-01 22:15:50 +03:00
}
2020-10-16 23:03:30 +03:00
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
2020-06-13 11:19:39 +03:00
entities . push ( {
2020-10-16 23:03:30 +03:00
_ : 'messageEntityMention' ,
offset : matchIndex + match [ 1 ] . length ,
length : match [ 2 ] . length + match [ 3 ] . length
2020-06-13 11:19:39 +03:00
} ) ;
2020-10-16 23:03:30 +03:00
} 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://' ;
2020-06-13 11:19:39 +03:00
}
2020-10-16 23:03:30 +03:00
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
} ) ;
2020-06-13 11:19:39 +03:00
}
}
2020-10-16 23:03:30 +03:00
} 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 ) {
2020-06-13 11:19:39 +03:00
entities . push ( {
2020-10-16 23:03:30 +03:00
_ : 'messageEntityEmoji' ,
2020-06-13 11:19:39 +03:00
offset : matchIndex ,
2020-10-16 23:03:30 +03:00
length : match [ 8 ] . length ,
unicode : emojiCoords
2020-06-13 11:19:39 +03:00
} ) ;
}
2020-10-16 23:03:30 +03:00
} else if ( match [ 11 ] ) { // Hashtag
2020-06-13 11:19:39 +03:00
entities . push ( {
2020-10-16 23:03:30 +03:00
_ : '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 )
2020-06-13 11:19:39 +03:00
} ) ;
}
2020-10-16 23:03:30 +03:00
raw = raw . substr ( match . index + match [ 0 ] . length ) ;
rawOffset += match . index + match [ 0 ] . length ;
2020-06-13 11:19:39 +03:00
}
2020-10-16 23:03:30 +03:00
// if (entities.length) {
// console.log('parse entities', text, entities.slice())
// }
return entities ;
2020-06-13 11:19:39 +03:00
}
2020-10-16 23:03:30 +03:00
/ * e x p o r t f u n c t i o n p a r s e E m o j i s ( t e x t : s t r i n g ) {
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
} )
} * /
2020-08-29 18:10:04 +03:00
2020-11-14 04:27:13 +02:00
export function parseMarkdown ( text : string , entities : MessageEntity [ ] , noTrim? : any ) : string {
/ * i f ( ! m a r k d o w n T e s t R e g E x p . t e s t ( t e x t ) ) {
2020-10-16 23:03:30 +03:00
return noTrim ? text : text.trim ( ) ;
2020-11-14 04:27:13 +02:00
} * /
2020-10-16 23:03:30 +03:00
var raw = text ;
var match ;
var newText : any = [ ] ;
var rawOffset = 0 ;
var matchIndex ;
2020-11-14 04:27:13 +02:00
while ( match = raw . match ( markdownRegExp ) ) {
matchIndex = rawOffset + match . index ;
newText . push ( raw . substr ( 0 , match . index ) ) ;
var 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 ;
2020-10-16 23:03:30 +03:00
}
2020-11-14 04:27:13 +02:00
newText . push ( match [ 1 ] + text + match [ 5 ] ) ;
2020-10-16 23:03:30 +03:00
entities . push ( {
_ : 'messageEntityPre' ,
language : '' ,
offset : matchIndex + match [ 1 ] . length ,
length : text.length
2020-11-14 04:27:13 +02:00
} ) ;
rawOffset -= match [ 2 ] . length + match [ 4 ] . length ;
} else if ( match [ 7 ] ) { // code|italic|bold
newText . push ( match [ 6 ] + text + match [ 9 ] ) ;
2020-10-16 23:03:30 +03:00
entities . push ( {
// @ts-ignore
_ : markdownEntities [ match [ 7 ] ] ,
offset : matchIndex + match [ 6 ] . length ,
length : text.length
2020-11-14 04:27:13 +02:00
} ) ;
rawOffset -= match [ 7 ] . length * 2 ;
} else if ( match [ 11 ] ) { // custom mention
2020-10-16 23:03:30 +03:00
newText . push ( text )
entities . push ( {
_ : 'messageEntityMentionName' ,
user_id : + match [ 10 ] ,
offset : matchIndex ,
length : text.length
2020-11-14 04:27:13 +02:00
} ) ;
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 ;
2020-10-16 23:03:30 +03:00
}
2020-11-14 04:27:13 +02:00
raw = raw . substr ( match . index + match [ 0 ] . length ) ;
rawOffset += match . index + match [ 0 ] . length ;
2020-06-13 11:19:39 +03:00
}
2020-11-14 04:27:13 +02:00
newText . push ( raw ) ;
newText = newText . join ( '' ) ;
if ( ! newText . replace ( /\s+/g , '' ) . length ) {
newText = text ;
entities . splice ( 0 , entities . length ) ;
2020-06-13 11:19:39 +03:00
}
2020-11-14 04:27:13 +02:00
if ( ! entities . length && ! noTrim ) {
newText = newText . trim ( ) ;
2020-10-16 23:03:30 +03:00
}
2020-11-14 04:27:13 +02:00
return newText ;
2020-10-16 23:03:30 +03:00
}
2020-08-29 18:10:04 +03:00
2020-10-16 23:03:30 +03:00
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 ] ;
/ * i f ( f r o m A p i & &
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 ) ) ;
2020-06-13 11:19:39 +03:00
}
2020-10-16 23:03:30 +03:00
bad = true ;
break ;
}
if ( cStart >= end ) {
break ;
2020-06-13 11:19:39 +03:00
}
}
2020-10-16 23:03:30 +03:00
if ( bad ) {
continue ;
2020-06-13 11:19:39 +03:00
}
2020-10-16 23:03:30 +03:00
totalEntities . push ( curEntity ) ;
2020-06-13 11:19:39 +03:00
}
2020-10-16 23:03:30 +03:00
totalEntities . sort ( ( a , b ) = > {
return a . offset - b . offset ;
} ) ;
// console.log('merge', currentEntities, newEntities, totalEntities)
return totalEntities ;
}
2020-08-29 18:10:04 +03:00
2020-10-16 23:03:30 +03:00
export function wrapRichNestedText ( text : string , nested : MessageEntity [ ] , options : any ) {
if ( nested === undefined ) {
return encodeEntities ( text ) ;
2020-06-13 11:19:39 +03:00
}
2020-10-16 23:03:30 +03:00
options . hasNested = true ;
return wrapRichText ( text , { entities : nested , nested : true } ) ;
2020-06-13 11:19:39 +03:00
}
2020-08-29 18:10:04 +03:00
2020-10-16 23:03:30 +03:00
export function wrapRichText ( text : string , options : Partial < {
entities : MessageEntity [ ] ,
contextSite : string ,
highlightUsername : string ,
2020-10-21 02:25:36 +03:00
noLinks : true ,
noLinebreaks : true ,
noCommands : true ,
2020-10-16 23:03:30 +03:00
fromBot : boolean ,
2020-10-21 02:25:36 +03:00
noTextFormat : true ,
2020-11-14 04:27:13 +02:00
passEntities : Partial < {
[ _ in MessageEntity [ '_' ] ] : true
} > ,
2020-10-21 02:25:36 +03:00
nested? : true ,
2020-10-16 23:03:30 +03:00
contextHashtag? : string
} > = { } ) {
if ( ! text || ! text . length ) {
return '' ;
}
2020-11-14 04:27:13 +02:00
const passEntities : typeof options . passEntities = options . passEntities || { } ;
2020-10-16 23:03:30 +03:00
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 ) ) ,
'">' ,
encodeEntities ( entityText ) ,
'</a>'
)
break ;
2020-10-03 20:45:56 +03:00
2020-10-16 23:03:30 +03:00
case 'messageEntityMentionName' :
if ( options . noLinks ) {
skipEntity = true ;
break ;
}
2020-06-13 11:19:39 +03:00
2020-10-16 23:03:30 +03:00
html . push (
'<a href="#/im?p=u' ,
encodeURIComponent ( entity . user_id ) ,
'">' ,
encodeEntities ( entityText ) ,
'</a>'
) ;
break ;
2020-06-13 11:19:39 +03:00
2020-10-16 23:03:30 +03:00
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 ;
}
2020-06-13 11:19:39 +03:00
html . push (
'<a href="' ,
2020-10-16 23:03:30 +03:00
encodeEntities ( 'mailto:' + entityText ) ,
2020-06-13 11:19:39 +03:00
'" target="_blank" rel="noopener noreferrer">' ,
2020-10-16 23:03:30 +03:00
encodeEntities ( entityText ) ,
2020-06-13 11:19:39 +03:00
'</a>'
2020-10-16 23:03:30 +03:00
) ;
2020-06-13 11:19:39 +03:00
break ;
2020-10-16 23:03:30 +03:00
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 ) ) ;
}
2020-11-14 04:27:13 +02:00
if ( options . noLinks && ! passEntities [ entity . _ ] ) {
2020-10-16 23:03:30 +03:00
html . push ( inner ) ;
} else {
html . push (
'<a href="' ,
encodeEntities ( url ) ,
'" target="_blank" rel="noopener noreferrer">' ,
inner ,
'</a>'
) ;
}
2020-06-13 11:19:39 +03:00
break ;
2020-10-16 23:03:30 +03:00
case 'messageEntityLinebreak' :
html . push ( options . noLinebreaks ? ' ' : '<br/>' ) ;
2020-06-13 11:19:39 +03:00
break ;
2020-10-16 23:03:30 +03:00
case 'messageEntityEmoji' :
2020-11-16 04:23:37 +02:00
html . push ( emojiSupported ? // ! contenteditable="false" нужен для поля ввода, иначе там будет меняться шрифт в Safari
` <span contenteditable="false" class="emoji"> ${ encodeEntities ( entityText ) } </span> ` :
2020-10-16 23:03:30 +03:00
` <img src="assets/img/emoji/ ${ entity . unicode } .png" alt=" ${ encodeEntities ( entityText ) } " class="emoji"> ` ) ;
2020-06-13 11:19:39 +03:00
break ;
2020-10-16 23:03:30 +03:00
case 'messageEntityBotCommand' :
if ( options . noLinks || options . noCommands || contextExternal ) {
skipEntity = true ;
break ;
}
2020-08-29 18:10:04 +03:00
2020-10-16 23:03:30 +03:00
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 ;
}
2020-08-29 18:10:04 +03:00
2020-10-16 23:03:30 +03:00
html . push (
'<a href="' ,
encodeEntities ( 'tg://bot_command?command=' + encodeURIComponent ( command ) + ( bot ? '&bot=' + encodeURIComponent ( bot ) : '' ) ) ,
'">' ,
encodeEntities ( entityText ) ,
'</a>'
) ;
break ;
2020-08-29 18:10:04 +03:00
2020-10-16 23:03:30 +03:00
case 'messageEntityBold' :
if ( options . noTextFormat ) {
html . push ( wrapRichNestedText ( entityText , entity . nested , options ) ) ;
break ;
}
html . push (
'<strong>' ,
wrapRichNestedText ( entityText , entity . nested , options ) ,
'</strong>'
) ;
break ;
2020-08-29 18:10:04 +03:00
2020-10-16 23:03:30 +03:00
case 'messageEntityItalic' :
if ( options . noTextFormat ) {
html . push ( wrapRichNestedText ( entityText , entity . nested , options ) ) ;
break ;
}
html . push (
'<em>' ,
wrapRichNestedText ( entityText , entity . nested , options ) ,
'</em>'
) ;
break ;
2020-08-29 18:10:04 +03:00
2020-10-16 23:03:30 +03:00
case 'messageEntityHighlight' :
html . push (
'<i>' ,
wrapRichNestedText ( entityText , entity . nested , options ) ,
'</i>'
) ;
break ;
2020-08-29 18:10:04 +03:00
2020-10-16 23:03:30 +03:00
case 'messageEntityStrike' :
html . push (
'<del>' ,
wrapRichNestedText ( entityText , entity . nested , options ) ,
'</del>'
) ;
break ;
2020-08-29 18:10:04 +03:00
2020-11-14 04:27:13 +02:00
case 'messageEntityUnderline' :
html . push (
'<u>' ,
wrapRichNestedText ( entityText , entity . nested , options ) ,
'</u>'
) ;
break ;
2020-10-16 23:03:30 +03:00
case 'messageEntityCode' :
if ( options . noTextFormat ) {
html . push ( encodeEntities ( entityText ) ) ;
break ;
}
html . push (
'<code>' ,
encodeEntities ( entityText ) ,
'</code>'
) ;
break ;
2020-08-29 18:10:04 +03:00
2020-10-16 23:03:30 +03:00
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 ;
}
2020-06-13 11:19:39 +03:00
2020-10-16 23:03:30 +03:00
lastOffset = entity . offset + ( skipEntity ? 0 : entity.length ) ;
2020-06-13 11:19:39 +03:00
}
2020-10-16 23:03:30 +03:00
html . push ( encodeEntities ( text . substr ( lastOffset ) ) ) ; // may be empty string
//console.log(html);
text = html . join ( '' ) ;
2020-06-13 11:19:39 +03:00
return text ;
}
2020-11-14 04:27:13 +02:00
/ * e x p o r t f u n c t i o n w r a p D r a f t T e x t ( t e x t : s t r i n g , o p t i o n s : a n y = { } ) {
2020-10-16 23:03:30 +03:00
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 ( '' ) ;
2020-11-14 04:27:13 +02:00
} * /
export function wrapDraftText ( text : string , options : Partial < {
entities : MessageEntity [ ]
} > = { } ) {
return wrapRichText ( text , {
. . . options ,
noLinks : true ,
passEntities : {
messageEntityTextUrl : true
}
} ) ;
2020-06-13 11:19:39 +03:00
}
2020-11-14 04:27:13 +02:00
//const draftEntityTypes: MessageEntity['_'][] = (['messageEntityTextUrl', 'messageEntityEmoji'] as MessageEntity['_'][]).concat(Object.values(markdownEntities) as any);
/ * c o n s t d r a f t E n t i t y T y p e s : P a r t i a l < { [ _ i n M e s s a g e E n t i t y [ ' _ ' ] ] : t r u e } > = {
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 } ) ;
} * /
2020-10-16 23:03:30 +03:00
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 ] ) ;
}
2020-06-13 11:19:39 +03:00
} else {
text . push ( match [ 0 ] ) ;
}
2020-10-16 23:03:30 +03:00
raw = raw . substr ( match . index + match [ 0 ] . length ) ;
2020-06-13 11:19:39 +03:00
}
2020-10-16 23:03:30 +03:00
text . push ( raw ) ;
return text . join ( '' ) ;
2020-06-13 11:19:39 +03:00
}
2020-10-16 23:03:30 +03:00
export function wrapEmojiText ( text : string ) {
if ( ! text ) return '' ;
let entities = parseEntities ( text ) . filter ( e = > e . _ == 'messageEntityEmoji' ) ;
return wrapRichText ( text , { entities } ) ;
2020-06-13 11:19:39 +03:00
}
2020-08-29 18:10:04 +03:00
2020-10-31 01:30:43 +02:00
export function wrapUrl ( url : string , unsafe? : number | boolean ) : string {
2020-10-16 23:03:30 +03:00
if ( ! url . match ( /^https?:\/\//i ) ) {
2020-10-16 23:22:41 +03:00
url = 'https://' + url ;
2020-10-16 23:03:30 +03:00
}
var tgMeMatch ;
var telescoPeMatch ;
2020-10-17 00:08:16 +03:00
/ * i f ( u n s a f e = = 2 ) {
2020-10-16 23:03:30 +03:00
url = 'tg://unsafe_url?url=' + encodeURIComponent ( url ) ;
2020-10-17 00:08:16 +03:00
} else * /if((tgMeMatch = url.match(/ ^ https ? : \ / \ / t ( ? : elegram ) ? \ . me \ / ( . + ) / ) ) ) {
2020-10-16 23:03:30 +03:00
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 ) ;
}
2020-06-13 11:19:39 +03:00
}
2020-10-16 23:03:30 +03:00
url = 'tg://resolve?domain=' + domain + ( query ? '&' + query : '' ) ;
2020-06-13 11:19:39 +03:00
}
2020-10-16 23:03:30 +03:00
}
} else if ( ( telescoPeMatch = url . match ( /^https?:\/\/telesco\.pe\/([^/?]+)\/(\d+)/ ) ) ) {
url = 'tg://resolve?domain=' + telescoPeMatch [ 1 ] + '&post=' + telescoPeMatch [ 2 ] ;
2020-10-17 00:08:16 +03:00
} / * else if ( unsafe ) {
url = 'tg://unsafe_url?url=' + encodeURIComponent ( url ) ;
} * /
2020-10-16 23:03:30 +03:00
return url ;
}
export function matchUrl ( text : string ) {
return text . match ( urlRegExp ) ;
2020-06-13 11:19:39 +03:00
}
2020-10-29 18:26:08 +02:00
2020-11-19 01:51:39 +02:00
/ * c o n s t e l = d o c u m e n t . c r e a t e E l e m e n t ( ' s p a n ' ) ;
2020-10-29 18:26:08 +02:00
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 ] ;
2020-11-14 01:04:03 +02:00
if ( 'length' in firstNode ) first = ( firstNode as any ) . textContent . trim ( ) . charAt ( 0 ) . toUpperCase ( ) ;
2020-10-29 18:26:08 +02:00
else first = ( firstNode as HTMLElement ) . outerHTML ;
if ( onlyFirst ) return first ;
if ( str . indexOf ( ' ' ) !== - 1 ) {
const lastNode = childNodes [ childNodes . length - 1 ] ;
2020-11-14 01:04:03 +02:00
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 ( ) ;
2020-10-29 18:26:08 +02:00
else last = ( lastNode as HTMLElement ) . outerHTML ;
}
return first + last ;
2020-11-19 01:51:39 +02:00
} * /
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 ) ;
2020-10-29 18:26:08 +02:00
}
2020-10-03 20:45:56 +03:00
}
2020-09-29 05:03:26 +03:00
MOUNT_CLASS_TO && ( MOUNT_CLASS_TO . RichTextProcessor = RichTextProcessor ) ;
2020-06-13 11:19:39 +03:00
export { RichTextProcessor } ;
2020-10-16 23:03:30 +03:00
export default RichTextProcessor ;
2020-06-13 11:19:39 +03:00