|
|
|
@ -1060,6 +1060,611 @@ angular.module('izhukov.utils', [])
|
|
|
|
|
} |
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
.service('RichTextProcessor', function ($sce, $sanitize) { |
|
|
|
|
|
|
|
|
|
var emojiMap = {}, |
|
|
|
|
emojiData = Config.Emoji, |
|
|
|
|
emojiIconSize = 18, |
|
|
|
|
emojiSupported = navigator.userAgent.search(/OS X|iPhone|iPad|iOS|Android/i) != -1, |
|
|
|
|
emojiCode; |
|
|
|
|
|
|
|
|
|
var emojiRegExp = "\\u0023\\u20E3|\\u00a9|\\u00ae|\\u203c|\\u2049|\\u2139|[\\u2194-\\u2199]|\\u21a9|\\u21aa|\\u231a|\\u231b|\\u23e9|[\\u23ea-\\u23ec]|\\u23f0|\\u24c2|\\u25aa|\\u25ab|\\u25b6|\\u2611|\\u2614|\\u26fd|\\u2705|\\u2709|[\\u2795-\\u2797]|\\u27a1|\\u27b0|\\u27bf|\\u2934|\\u2935|[\\u2b05-\\u2b07]|\\u2b1b|\\u2b1c|\\u2b50|\\u2b55|\\u3030|\\u303d|\\u3297|\\u3299|[\\uE000-\\uF8FF\\u270A-\\u2764\\u2122\\u25C0\\u25FB-\\u25FE\\u2615\\u263a\\u2648-\\u2653\\u2660-\\u2668\\u267B\\u267F\\u2693\\u261d\\u26A0-\\u26FA\\u2708\\u2702\\u2601\\u260E]|[\\u2600\\u26C4\\u26BE\\u23F3\\u2764]|\\uD83D[\\uDC00-\\uDFFF]|\\uD83C[\\uDDE8-\\uDDFA\uDDEC]\\uD83C[\\uDDEA-\\uDDFA\uDDE7]|[0-9]\\u20e3|\\uD83C[\\uDC00-\\uDFFF]"; |
|
|
|
|
|
|
|
|
|
for (emojiCode in emojiData) { |
|
|
|
|
emojiMap[emojiData[emojiCode][0]] = emojiCode; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var 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)
|
|
|
|
|
|
|
|
|
|
var alphaNumericRegExp = "0-9\_" + alphaCharsRegExp; |
|
|
|
|
|
|
|
|
|
// Based on Regular Expression for URL validation by Diego Perini
|
|
|
|
|
var 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 + "0-9\-]{0,64}" + |
|
|
|
|
// domain name
|
|
|
|
|
"(?:\\.[" + alphaCharsRegExp + "0-9][" + alphaCharsRegExp + "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.;,(\\[\\]{}<>\"'])?)?"; |
|
|
|
|
|
|
|
|
|
var usernameRegExp = "[a-zA-Z\\d_]{5,32}"; |
|
|
|
|
var botCommandRegExp = "\\/([a-zA-Z\\d_]{1,32})(?:@(" + usernameRegExp + "))?(\\b|$)" |
|
|
|
|
|
|
|
|
|
var fullRegExp = new RegExp('(^| )(@)(' + usernameRegExp + ')|(' + urlRegExp + ')|(\\n)|(' + emojiRegExp + ')|(^|\\s)(#[' + alphaNumericRegExp + ']{2,64})|(^|\\s)' + botCommandRegExp, 'i'); |
|
|
|
|
|
|
|
|
|
var 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,}))$/; |
|
|
|
|
var youtubeRegExp = /^(?:https?:\/\/)?(?:www\.)?youtu(?:|\.be|be\.com|\.b)(?:\/v\/|\/watch\\?v=|e\/|(?:\/\??#)?\/watch(?:.+)v=)(.{11})(?:\&[^\s]*)?/; |
|
|
|
|
var vimeoRegExp = /^(?:https?:\/\/)?(?:www\.)?vimeo\.com\/(\d+)/; |
|
|
|
|
var instagramRegExp = /^https?:\/\/(?:instagr\.am\/p\/|instagram\.com\/p\/)([a-zA-Z0-9\-\_]+)/i; |
|
|
|
|
var vineRegExp = /^https?:\/\/vine\.co\/v\/([a-zA-Z0-9\-\_]+)/i; |
|
|
|
|
var twitterRegExp = /^https?:\/\/twitter\.com\/.+?\/status\/\d+/i; |
|
|
|
|
var facebookRegExp = /^https?:\/\/(?:www\.|m\.)?facebook\.com\/(?:.+?\/posts\/\d+|(?:story\.php|permalink\.php)\?story_fbid=(\d+)(?:&substory_index=\d+)?&id=(\d+))/i; |
|
|
|
|
var gplusRegExp = /^https?:\/\/plus\.google\.com\/\d+\/posts\/[a-zA-Z0-9\-\_]+/i; |
|
|
|
|
var soundcloudRegExp = /^https?:\/\/(?:soundcloud\.com|snd\.sc)\/([a-zA-Z0-9%\-\_]+)\/([a-zA-Z0-9%\-\_]+)/i; |
|
|
|
|
var spotifyRegExp = /(https?:\/\/(open\.spotify\.com|play\.spotify\.com|spoti\.fi)\/(.+)|spotify:(.+))/i; |
|
|
|
|
|
|
|
|
|
var markdownRegExp = /(^|\n)```(.{0,16})\n([\s\S]+?)\n```(\n|$)|(^|\s)`([^\n]+?)`/; |
|
|
|
|
|
|
|
|
|
var siteHashtags = { |
|
|
|
|
Telegram: '#/im?q=%23{1}', |
|
|
|
|
Twitter: 'https://twitter.com/hashtag/{1}', |
|
|
|
|
Instagram: 'https://instagram.com/explore/tags/{1}/', |
|
|
|
|
'Google Plus': 'https://plus.google.com/explore/{1}' |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
var siteMentions = { |
|
|
|
|
Telegram: '#/im?p=%40{1}', |
|
|
|
|
Twitter: 'https://twitter.com/{1}', |
|
|
|
|
Instagram: 'https://instagram.com/{1}/', |
|
|
|
|
GitHub: 'https://github.com/{1}' |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
return { |
|
|
|
|
wrapRichText: wrapRichText, |
|
|
|
|
wrapPlainText: wrapPlainText, |
|
|
|
|
parseEntities: parseEntities, |
|
|
|
|
parseMarkdown: parseMarkdown, |
|
|
|
|
mergeEntities: mergeEntities |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
function getEmojiSpritesheetCoords(emojiCode) { |
|
|
|
|
var i, row, column, totalColumns; |
|
|
|
|
for (var cat = 0; cat < Config.EmojiCategories.length; cat++) { |
|
|
|
|
totalColumns = Config.EmojiCategorySpritesheetDimens[cat][1]; |
|
|
|
|
i = Config.EmojiCategories[cat].indexOf(emojiCode); |
|
|
|
|
if (i > -1) { |
|
|
|
|
row = Math.floor(i / totalColumns); |
|
|
|
|
column = (i % totalColumns); |
|
|
|
|
return { category: cat, row: row, column: column }; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
console.error('emoji not found in spritesheet', emojiCode); |
|
|
|
|
return null; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function parseEntities (text, options) { |
|
|
|
|
options = options || {}; |
|
|
|
|
|
|
|
|
|
var match, |
|
|
|
|
raw = text, |
|
|
|
|
url, |
|
|
|
|
entities = [], |
|
|
|
|
emojiCode, |
|
|
|
|
emojiCoords, |
|
|
|
|
matchIndex, |
|
|
|
|
rawOffset = 0; |
|
|
|
|
|
|
|
|
|
// var start = tsNow();
|
|
|
|
|
|
|
|
|
|
while ((match = raw.match(fullRegExp))) { |
|
|
|
|
matchIndex = rawOffset + match.index; |
|
|
|
|
|
|
|
|
|
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 = false, |
|
|
|
|
protocol = match[5], |
|
|
|
|
tld = match[6], |
|
|
|
|
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]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var tgMeMatch; |
|
|
|
|
if (tld == 'me' && |
|
|
|
|
(tgMeMatch = url.match(/^https?:\/\/telegram\.me\/(.+)/))) { |
|
|
|
|
var path = tgMeMatch[1].split('/'); |
|
|
|
|
switch (path[0]) { |
|
|
|
|
case 'joinchat': |
|
|
|
|
url = 'tg://join?invite=' + path[1]; |
|
|
|
|
break; |
|
|
|
|
case 'addstickers': |
|
|
|
|
url = 'tg://addstickers?set=' + path[1]; |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
var domainQuery = path[0].split('?'); |
|
|
|
|
url = 'tg://resolve?domain=' + domainQuery[0] + (domainQuery[1] ? '&' + domainQuery[1] : ''); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} 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
|
|
|
|
|
if ((emojiCode = emojiMap[match[8]]) && |
|
|
|
|
(emojiCoords = getEmojiSpritesheetCoords(emojiCode))) { |
|
|
|
|
|
|
|
|
|
entities.push({ |
|
|
|
|
_: 'messageEntityEmoji', |
|
|
|
|
offset: matchIndex, |
|
|
|
|
length: match[0].length, |
|
|
|
|
coords: emojiCoords, |
|
|
|
|
title: emojiData[emojiCode][1][0] |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
else if (match[10]) { // Hashtag
|
|
|
|
|
entities.push({ |
|
|
|
|
_: 'messageEntityHashtag', |
|
|
|
|
offset: matchIndex + match[9].length, |
|
|
|
|
length: match[10].length |
|
|
|
|
}); |
|
|
|
|
} |
|
|
|
|
else if (match[12]) { // Bot command
|
|
|
|
|
entities.push({ |
|
|
|
|
_: 'messageEntityBotCommand', |
|
|
|
|
offset: matchIndex + match[11].length, |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function parseMarkdown (text, entities) { |
|
|
|
|
if (text.indexOf('`') == -1) { |
|
|
|
|
return text; |
|
|
|
|
} |
|
|
|
|
var raw = text; |
|
|
|
|
var match; |
|
|
|
|
var newText = []; |
|
|
|
|
while (match = raw.match(markdownRegExp)) { |
|
|
|
|
newText.push(raw.substr(0, match.index)); |
|
|
|
|
|
|
|
|
|
if (match[3]) { // pre
|
|
|
|
|
newText.push(match[1] + match[3] + match[4]); |
|
|
|
|
entities.push({ |
|
|
|
|
_: 'messageEntityPre', |
|
|
|
|
language: match[2] || '', |
|
|
|
|
offset: match.index + match[1].length, |
|
|
|
|
length: match[3].length |
|
|
|
|
}) |
|
|
|
|
} else { // code
|
|
|
|
|
newText.push(match[5] + match[6]); |
|
|
|
|
entities.push({ |
|
|
|
|
_: 'messageEntityCode', |
|
|
|
|
offset: match.index + match[5].length, |
|
|
|
|
length: match[6].length |
|
|
|
|
}) |
|
|
|
|
} |
|
|
|
|
raw = raw.substr(match.index + match[0].length); |
|
|
|
|
} |
|
|
|
|
newText.push(raw); |
|
|
|
|
return newText.join(''); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function mergeEntities (currentEntities, newEntities, fromApi) { |
|
|
|
|
var totalEntities = newEntities.slice(); |
|
|
|
|
|
|
|
|
|
var i, len = currentEntities.length; |
|
|
|
|
var j, len2 = newEntities.length; |
|
|
|
|
var startJ = 0; |
|
|
|
|
var curEntity, newEntity; |
|
|
|
|
var start, end, cStart, cEnd, bad; |
|
|
|
|
for (i = 0; i < len; i++) { |
|
|
|
|
curEntity = currentEntities[i]; |
|
|
|
|
if (fromApi && |
|
|
|
|
curEntity._ != 'messageEntityLinebreak' && |
|
|
|
|
curEntity._ != 'messageEntityEmoji') { |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
// console.log('s', curEntity, newEntities);
|
|
|
|
|
start = curEntity.offset; |
|
|
|
|
end = start + curEntity.length; |
|
|
|
|
bad = false; |
|
|
|
|
for (j = startJ; j < len2; j++) { |
|
|
|
|
newEntity = newEntities[j]; |
|
|
|
|
cStart = newEntity.offset; |
|
|
|
|
cEnd = cStart + newEntity.length; |
|
|
|
|
if (cStart <= start) { |
|
|
|
|
startJ = j; |
|
|
|
|
} |
|
|
|
|
if (start >= cStart && start < cEnd || |
|
|
|
|
end > cStart && end <= cEnd) { |
|
|
|
|
// console.log('bad', curEntity, newEntity);
|
|
|
|
|
bad = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
if (cStart >= end) { |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
if (bad) { |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
totalEntities.push(curEntity); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
totalEntities.sort(function (a, b) { |
|
|
|
|
return a.offset - b.offset; |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
// console.log('merge', currentEntities, newEntities, totalEntities);
|
|
|
|
|
|
|
|
|
|
return totalEntities; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function wrapRichText (text, options) { |
|
|
|
|
if (!text || !text.length) { |
|
|
|
|
return ''; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
options = options || {}; |
|
|
|
|
|
|
|
|
|
var entities = options.entities, |
|
|
|
|
contextSite = options.contextSite || 'Telegram', |
|
|
|
|
contextExternal = contextSite != 'Telegram', |
|
|
|
|
emojiFound = false; |
|
|
|
|
|
|
|
|
|
if (entities === undefined) { |
|
|
|
|
entities = parseEntities(text, options); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
var i = 0; |
|
|
|
|
var len = entities.length; |
|
|
|
|
var entity; |
|
|
|
|
var entityText; |
|
|
|
|
var skipEntity; |
|
|
|
|
var url; |
|
|
|
|
var html = []; |
|
|
|
|
var lastOffset = 0; |
|
|
|
|
for (i = 0; i < len; i++) { |
|
|
|
|
entity = entities[i]; |
|
|
|
|
if (entity.offset > lastOffset) { |
|
|
|
|
html.push( |
|
|
|
|
encodeEntities(text.substr(lastOffset, entity.offset - lastOffset)) |
|
|
|
|
); |
|
|
|
|
} |
|
|
|
|
else if (entity.offset < lastOffset) { |
|
|
|
|
continue; |
|
|
|
|
} |
|
|
|
|
skipEntity = false; |
|
|
|
|
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" ' : '', |
|
|
|
|
' href="', |
|
|
|
|
contextUrl.replace('{1}', encodeURIComponent(username)), |
|
|
|
|
'">', |
|
|
|
|
encodeEntities(entityText), |
|
|
|
|
'</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" ' : '', |
|
|
|
|
'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">', |
|
|
|
|
encodeEntities(entityText), |
|
|
|
|
'</a>' |
|
|
|
|
); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case 'messageEntityUrl': |
|
|
|
|
case 'messageEntityTextUrl': |
|
|
|
|
if (options.noLinks) { |
|
|
|
|
skipEntity = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
var url = entity.url || entityText; |
|
|
|
|
if (!url.match(/^https?:\/\//i)) { |
|
|
|
|
url = 'http://' + url; |
|
|
|
|
} |
|
|
|
|
var tgMeMatch; |
|
|
|
|
if ((tgMeMatch = url.match(/^https?:\/\/telegram\.me\/(.+)/))) { |
|
|
|
|
var path = tgMeMatch[1].split('/'); |
|
|
|
|
switch (path[0]) { |
|
|
|
|
case 'joinchat': |
|
|
|
|
url = 'tg://join?invite=' + path[1]; |
|
|
|
|
break; |
|
|
|
|
case 'addstickers': |
|
|
|
|
url = 'tg://addstickers?set=' + path[1]; |
|
|
|
|
break; |
|
|
|
|
default: |
|
|
|
|
var domainQuery = path[0].split('?'); |
|
|
|
|
url = 'tg://resolve?domain=' + domainQuery[0] + (domainQuery[1] ? '&' + domainQuery[1] : ''); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
html.push( |
|
|
|
|
'<a href="', |
|
|
|
|
encodeEntities(url), |
|
|
|
|
'" target="_blank">', |
|
|
|
|
encodeEntities(entityText), |
|
|
|
|
'</a>' |
|
|
|
|
); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case 'messageEntityLinebreak': |
|
|
|
|
html.push(options.noLinebreaks ? ' ' : '<br/>'); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case 'messageEntityEmoji': |
|
|
|
|
html.push( |
|
|
|
|
'<span class="emoji emoji-', |
|
|
|
|
entity.coords.category, |
|
|
|
|
'-', |
|
|
|
|
(emojiIconSize * entity.coords.column), |
|
|
|
|
'-', |
|
|
|
|
(emojiIconSize * entity.coords.row), |
|
|
|
|
'" ', |
|
|
|
|
'title="', entity.title, '">', |
|
|
|
|
':', entity.title, ':</span>' |
|
|
|
|
); |
|
|
|
|
emojiFound = true; |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case 'messageEntityBotCommand': |
|
|
|
|
if (options.noLinks || options.noCommands || contextExternal) { |
|
|
|
|
skipEntity = true; |
|
|
|
|
break; |
|
|
|
|
} |
|
|
|
|
var command = entityText; |
|
|
|
|
var bot, atPos; |
|
|
|
|
if ((atPos = command.indexOf('@')) != -1) { |
|
|
|
|
bot = command.substr(atPos); |
|
|
|
|
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': |
|
|
|
|
html.push( |
|
|
|
|
'<strong>', |
|
|
|
|
encodeEntities(entityText), |
|
|
|
|
'</strong>' |
|
|
|
|
); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case 'messageEntityItalic': |
|
|
|
|
html.push( |
|
|
|
|
'<em>', |
|
|
|
|
encodeEntities(entityText), |
|
|
|
|
'</em>' |
|
|
|
|
); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case 'messageEntityCode': |
|
|
|
|
html.push( |
|
|
|
|
'<code>', |
|
|
|
|
encodeEntities(entityText), |
|
|
|
|
'</code>' |
|
|
|
|
); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
case 'messageEntityPre': |
|
|
|
|
html.push( |
|
|
|
|
'<pre><code',(entity.language ? ' class="language-' + encodeEntities(entity.language) +'"' : ''),'>', |
|
|
|
|
encodeEntities(entityText), |
|
|
|
|
'</code></pre>' |
|
|
|
|
); |
|
|
|
|
break; |
|
|
|
|
|
|
|
|
|
default: |
|
|
|
|
skipEntity = true; |
|
|
|
|
} |
|
|
|
|
if (!skipEntity) { |
|
|
|
|
lastOffset = entity.offset + entity.length; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
html.push(encodeEntities(text.substr(lastOffset))); |
|
|
|
|
|
|
|
|
|
text = $sanitize(html.join('')); |
|
|
|
|
|
|
|
|
|
if (emojiFound) { |
|
|
|
|
text = text.replace(/\ufe0f|️|�|‍/g, '', text); |
|
|
|
|
text = text.replace(/<span class="emoji emoji-(\d)-(\d+)-(\d+)"(.+?)<\/span>/g, |
|
|
|
|
'<span class="emoji emoji-spritesheet-$1" style="background-position: -$2px -$3px;" $4</span>'); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
return $sce.trustAs('html', text); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function checkBrackets(url) { |
|
|
|
|
var urlLength = url.length, |
|
|
|
|
urlOpenBrackets = url.split('(').length - 1, |
|
|
|
|
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; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
function wrapPlainText (text, options) { |
|
|
|
|
if (emojiSupported) { |
|
|
|
|
return text; |
|
|
|
|
} |
|
|
|
|
if (!text || !text.length) { |
|
|
|
|
return ''; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
options = options || {}; |
|
|
|
|
|
|
|
|
|
text = text.replace(/\ufe0f/g, '', text); |
|
|
|
|
|
|
|
|
|
var match, |
|
|
|
|
raw = text, |
|
|
|
|
text = [], |
|
|
|
|
emojiTitle; |
|
|
|
|
|
|
|
|
|
while ((match = raw.match(fullRegExp))) { |
|
|
|
|
text.push(raw.substr(0, match.index)); |
|
|
|
|
|
|
|
|
|
if (match[8]) { |
|
|
|
|
if ((emojiCode = emojiMap[match[8]]) && |
|
|
|
|
(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(''); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
}) |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|