Telegram Web, preconfigured for usage in I2P.
http://web.telegram.i2p/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1439 lines
40 KiB
1439 lines
40 KiB
/*! |
|
* Webogram v0.1 - messaging web application for MTProto |
|
* https://github.com/zhukov/webogram |
|
* Copyright (C) 2014 Igor Zhukov <igor.beatle@gmail.com> |
|
* https://github.com/zhukov/webogram/blob/master/LICENSE |
|
*/ |
|
|
|
'use strict'; |
|
|
|
/* Services */ |
|
|
|
angular.module('myApp.services', []) |
|
|
|
.service('AppConfigManager', function ($q) { |
|
var testPrefix = window._testMode ? 't_' : ''; |
|
var cache = {}; |
|
var useLs = !window.chrome || !chrome.storage || !chrome.storage.local; |
|
|
|
function getValue() { |
|
var keys = Array.prototype.slice.call(arguments), |
|
result = [], |
|
single = keys.length == 1, |
|
allFound = true; |
|
|
|
for (var i = 0; i < keys.length; i++) { |
|
keys[i] = testPrefix + keys[i]; |
|
} |
|
|
|
angular.forEach(keys, function (key) { |
|
if (cache[key] !== undefined) { |
|
result.push(cache[key]); |
|
} |
|
else if (useLs) { |
|
var value = localStorage.getItem(key); |
|
value = (value === undefined || value === null) ? false : JSON.parse(value); |
|
result.push(cache[key] = value); |
|
} |
|
else { |
|
allFound = false; |
|
} |
|
}); |
|
|
|
if (allFound) { |
|
return $q.when(single ? result[0] : result); |
|
} |
|
|
|
var deferred = $q.defer(); |
|
|
|
// dLog('get', keys); |
|
chrome.storage.local.get(keys, function (resultObj) { |
|
// dLog('got', resultObj); |
|
result = []; |
|
angular.forEach(keys, function (key) { |
|
var value = resultObj[key]; |
|
// dLog('p1', key, value); |
|
value = value === undefined || value === null ? false : JSON.parse(value); |
|
// dLog('p2', value); |
|
result.push(cache[key] = value); |
|
}); |
|
|
|
// dLog('got parsed', result); |
|
deferred.resolve(single ? result[0] : result); |
|
}); |
|
|
|
return deferred.promise; |
|
}; |
|
|
|
function setValue(obj) { |
|
var keyValues = {}; |
|
angular.forEach(obj, function (value, key) { |
|
keyValues[testPrefix + key] = JSON.stringify(value); |
|
cache[testPrefix + key] = value; |
|
}); |
|
|
|
if (useLs) { |
|
angular.forEach(keyValues, function (value, key) { |
|
localStorage.setItem(key, value); |
|
}); |
|
return $q.when(); |
|
} |
|
|
|
var deferred = $q.defer(); |
|
|
|
chrome.storage.local.set(keyValues, function () { |
|
deferred.resolve(); |
|
}); |
|
|
|
return deferred.promise; |
|
}; |
|
|
|
function removeValue () { |
|
var keys = Array.prototype.slice.call(arguments); |
|
|
|
for (var i = 0; i < keys.length; i++) { |
|
keys[i] = testPrefix + keys[i]; |
|
} |
|
|
|
angular.forEach(keys, function(key){ |
|
delete cache[key]; |
|
}); |
|
|
|
if (useLs) { |
|
angular.forEach(keys, function(key){ |
|
localStorage.removeItem(key); |
|
}); |
|
|
|
return $q.when(); |
|
} |
|
|
|
var deferred = $q.defer(); |
|
|
|
chrome.storage.local.remove(keys, function () { |
|
deferred.resolve(); |
|
}); |
|
|
|
return deferred.promise; |
|
}; |
|
|
|
return { |
|
get: getValue, |
|
set: setValue, |
|
remove: removeValue |
|
}; |
|
}) |
|
|
|
.service('AppUsersManager', function ($rootScope, $modal, MtpApiFileManager, MtpApiManager, RichTextProcessor) { |
|
var users = {}; |
|
|
|
function saveApiUsers (apiUsers) { |
|
angular.forEach(apiUsers, saveApiUser); |
|
}; |
|
|
|
function saveApiUser (apiUser) { |
|
if (!angular.isObject(apiUser)) { |
|
return; |
|
} |
|
|
|
if (apiUser.first_name) { |
|
apiUser.rFirstName = RichTextProcessor.wrapRichText(apiUser.first_name, {noLinks: true, noLinebreaks: true}); |
|
apiUser.rFullName = RichTextProcessor.wrapRichText(apiUser.first_name + ' ' + (apiUser.last_name || ''), {noLinks: true, noLinebreaks: true}); |
|
} else { |
|
apiUser.rFirstName = RichTextProcessor.wrapRichText(apiUser.last_name, {noLinks: true, noLinebreaks: true}) || 'DELETED'; |
|
apiUser.rFullName = RichTextProcessor.wrapRichText(apiUser.last_name, {noLinks: true, noLinebreaks: true}) || 'DELETED'; |
|
} |
|
|
|
if (users[apiUser.id] === undefined) { |
|
users[apiUser.id] = apiUser; |
|
} else { |
|
angular.extend(users[apiUser.id], apiUser); |
|
} |
|
}; |
|
|
|
function getUser (id) { |
|
if (angular.isObject(id)) { |
|
return id; |
|
} |
|
return users[id] || {id: id, deleted: true}; |
|
} |
|
|
|
function getUserPhoto(id, placeholder) { |
|
var user = getUser(id); |
|
|
|
return { |
|
placeholder: 'img/placeholders/' + placeholder + 'Avatar'+((Math.abs(id) % 8) + 1)+'@2x.png', |
|
location: user && user.photo && user.photo.photo_small |
|
}; |
|
} |
|
|
|
function getUserString (id) { |
|
var user = getUser(id); |
|
return 'u' + id + (user.access_hash ? '_' + user.access_hash : ''); |
|
} |
|
|
|
function getUserInput (id) { |
|
var user = getUser(id); |
|
if (user._ == 'userSelf') { |
|
return {_: 'inputUserSelf'}; |
|
} |
|
return { |
|
_: 'inputUserForeign', |
|
user_id: id, |
|
access_hash: user.access_hash || 0 |
|
}; |
|
} |
|
|
|
function wrapForFull (id) { |
|
var user = getUser(id); |
|
|
|
user.thumb = { |
|
placeholder: 'img/placeholders/UserAvatar'+((Math.abs(id) % 8) + 1)+'@2x.png', |
|
location: user && user.photo && user.photo.photo_small, |
|
width: 120, |
|
height: 120, |
|
size: 0 |
|
}; |
|
user.peerString = getUserString(id); |
|
|
|
return user; |
|
} |
|
|
|
function openUser (userID, accessHash) { |
|
var scope = $rootScope.$new(); |
|
scope.userID = userID; |
|
|
|
var modalInstance = $modal.open({ |
|
templateUrl: 'partials/user_modal.html', |
|
controller: 'UserModalController', |
|
scope: scope, |
|
windowClass: 'user_modal_window', |
|
resolve: { |
|
userFull: MtpApiManager.invokeApi('users.getFullUser', { |
|
id: getUserInput(userID) |
|
}).then(function (result) { |
|
saveApiUser(result.user); |
|
return result; |
|
}) |
|
} |
|
}); |
|
} |
|
|
|
$rootScope.openUser = openUser; |
|
|
|
$rootScope.$on('apiUpdate', function (e, update) { |
|
// dLog('on apiUpdate', update); |
|
switch (update._) { |
|
case 'updateUserStatus': |
|
var userID = update.user_id; |
|
if (users[userID]) { |
|
users[userID].status = update.status; |
|
$rootScope.$broadcast('user_update', userID); |
|
} |
|
break; |
|
|
|
case 'updateUserPhoto': |
|
var userID = update.user_id; |
|
if (users[userID]) { |
|
users[userID].photo = update.photo; |
|
$rootScope.$broadcast('user_update', userID); |
|
} |
|
break; |
|
} |
|
}); |
|
|
|
|
|
return { |
|
saveApiUsers: saveApiUsers, |
|
saveApiUser: saveApiUser, |
|
getUser: getUser, |
|
getUserPhoto: getUserPhoto, |
|
getUserString: getUserString, |
|
wrapForFull: wrapForFull, |
|
openUser: openUser |
|
} |
|
}) |
|
|
|
.service('AppChatsManager', function ($rootScope, $modal, MtpApiFileManager, MtpApiManager, AppUsersManager, RichTextProcessor) { |
|
var chats = {}; |
|
|
|
function saveApiChats (apiChats) { |
|
angular.forEach(apiChats, saveApiChat); |
|
}; |
|
|
|
function saveApiChat (apiChat) { |
|
if (!angular.isObject(apiChat)) { |
|
return; |
|
} |
|
apiChat.rTitle = RichTextProcessor.wrapRichText(apiChat.title, {noLinks: true, noLinebreaks: true}) || 'DELETED'; |
|
if (chats[apiChat.id] === undefined) { |
|
chats[apiChat.id] = apiChat; |
|
} else { |
|
angular.extend(chats[apiChat.id], apiChat); |
|
} |
|
}; |
|
|
|
function getChat (id) { |
|
return chats[id] || {id: id, deleted: true}; |
|
} |
|
|
|
function getChatPhoto(id, placeholder) { |
|
var chat = getChat(id); |
|
|
|
return { |
|
placeholder: 'img/placeholders/' + placeholder + 'Avatar'+((Math.abs(id) % 4) + 1)+'@2x.png', |
|
location: chat && chat.photo && chat.photo.photo_small |
|
}; |
|
} |
|
|
|
function getChatString (id) { |
|
var chat = getChat(id); |
|
return 'g' + id; |
|
} |
|
|
|
function wrapForFull (id, fullChat) { |
|
var chatFull = angular.copy(fullChat), |
|
chat = getChat(id); |
|
|
|
if (chatFull.participants._ == 'chatParticipants') { |
|
angular.forEach(chatFull.participants.participants, function(participant){ |
|
participant.user = AppUsersManager.getUser(participant.user_id); |
|
participant.userPhoto = AppUsersManager.getUserPhoto(participant.user_id, 'User'); |
|
participant.inviter = AppUsersManager.getUser(participant.inviter_id); |
|
}); |
|
} |
|
|
|
chatFull.thumb = { |
|
placeholder: 'img/placeholders/GroupAvatar'+((Math.abs(id) % 4) + 1)+'@2x.png', |
|
location: chat && chat.photo && chat.photo.photo_small, |
|
width: 120, |
|
height: 120, |
|
size: 0 |
|
}; |
|
chatFull.peerString = getChatString(id); |
|
chatFull.chat = chat; |
|
|
|
return chatFull; |
|
} |
|
|
|
function openChat (chatID, accessHash) { |
|
var scope = $rootScope.$new(); |
|
scope.chatID = chatID; |
|
|
|
var modalInstance = $modal.open({ |
|
templateUrl: 'partials/chat_modal.html', |
|
controller: 'ChatModalController', |
|
windowClass: 'chat_modal_window', |
|
scope: scope, |
|
resolve: { |
|
fullChat: function () { |
|
return MtpApiManager.invokeApi('messages.getFullChat', { |
|
chat_id: chatID |
|
}).then(function (result) { |
|
saveApiChats(result.chats); |
|
AppUsersManager.saveApiUsers(result.users); |
|
return result.full_chat; |
|
}) |
|
} |
|
} |
|
}); |
|
} |
|
|
|
$rootScope.openChat = openChat; |
|
|
|
|
|
return { |
|
saveApiChats: saveApiChats, |
|
saveApiChat: saveApiChat, |
|
getChat: getChat, |
|
getChatPhoto: getChatPhoto, |
|
getChatString: getChatString, |
|
wrapForFull: wrapForFull, |
|
openChat: openChat |
|
} |
|
}) |
|
|
|
.service('AppPeersManager', function (AppUsersManager, AppChatsManager) { |
|
return { |
|
getInputPeer: function (peerString) { |
|
var isUser = peerString.charAt(0) == 'u', |
|
peerParams = peerString.substr(1).split('_'); |
|
|
|
return isUser |
|
? {_: 'inputPeerForeign', user_id: peerParams[0], access_hash: peerParams[1]} |
|
: {_: 'inputPeerChat', chat_id: peerParams[0]}; |
|
}, |
|
getInputPeerByID: function (peerID) { |
|
if (peerID > 0) { |
|
return { |
|
_: 'inputPeerForeign', |
|
user_id: peerID, |
|
access_hash: AppUsersManager.getUser(peerID).access_hash || 0 |
|
}; |
|
} else if (peerID < 0) { |
|
return { |
|
_: 'inputPeerChat', |
|
chat_id: -peerID |
|
}; |
|
} |
|
}, |
|
getOutputPeer: function (peerID) { |
|
return peerID > 0 |
|
? {_: 'peerUser', user_id: peerID} |
|
: {_: 'peerChat', chat_id: -peerID}; |
|
}, |
|
getPeerID: function (peerString) { |
|
if (angular.isObject(peerString)) { |
|
return peerString.user_id |
|
? peerString.user_id |
|
: -peerString.chat_id; |
|
} |
|
var isUser = peerString.charAt(0) == 'u', |
|
peerParams = peerString.substr(1).split('_'); |
|
|
|
return isUser ? peerParams[0] : -peerParams[0] || 0; |
|
}, |
|
getPeer: function (peerID) { |
|
return peerID > 0 |
|
? AppUsersManager.getUser(peerID) |
|
: AppChatsManager.getChat(-peerID); |
|
}, |
|
getPeerPhoto: function (peerID, userPlaceholder, chatPlaceholder) { |
|
return peerID > 0 |
|
? AppUsersManager.getUserPhoto(peerID, userPlaceholder) |
|
: AppChatsManager.getChatPhoto(-peerID, chatPlaceholder) |
|
} |
|
} |
|
}) |
|
|
|
.service('AppMessagesManager', function ($q, $rootScope, $filter, $sanitize, ApiUpdatesManager, AppUsersManager, AppChatsManager, AppPeersManager, AppPhotosManager, AppVideoManager, AppDocsManager, MtpApiManager, RichTextProcessor) { |
|
|
|
var messagesStorage = {}; |
|
var messagesForHistory = {}; |
|
var historiesStorage = {}; |
|
var dialogsStorage = {count: null, dialogs: []}; |
|
|
|
function getDialogs (offset, limit) { |
|
if (dialogsStorage.count !== null && dialogsStorage.dialogs.length >= offset + limit) { |
|
return $q.when({ |
|
count: dialogsStorage.count, |
|
dialogs: dialogsStorage.dialogs.slice(offset, offset + limit) |
|
}); |
|
} |
|
|
|
var deferred = $q.defer(); |
|
|
|
MtpApiManager.invokeApi('messages.getDialogs', { |
|
offset: offset, |
|
limit: limit, |
|
max_id: 0 |
|
}).then(function (dialogsResult) { |
|
AppUsersManager.saveApiUsers(dialogsResult.users); |
|
AppChatsManager.saveApiChats(dialogsResult.chats); |
|
saveMessages(dialogsResult.messages); |
|
|
|
dialogsStorage.count = dialogsResult._ == 'messages.dialogsSlice' |
|
? dialogsResult.count |
|
: dialogsResult.dialogs.length; |
|
|
|
angular.forEach(dialogsResult.dialogs, function (dialog) { |
|
dialogsStorage.dialogs.push({ |
|
peerID: AppPeersManager.getPeerID(dialog.peer), |
|
top_message: dialog.top_message, |
|
unread_count: dialog.unread_count |
|
}); |
|
}); |
|
|
|
deferred.resolve({ |
|
count: dialogsStorage.count, |
|
dialogs: dialogsStorage.dialogs.slice(offset, offset + limit) |
|
}); |
|
}, function (error) { |
|
deferred.reject(error); |
|
}); |
|
|
|
return deferred.promise; |
|
} |
|
|
|
function getHistory (inputPeer, maxID, limit) { |
|
|
|
var peerID = AppPeersManager.getPeerID(inputPeer), |
|
historyStorage = historiesStorage[peerID], |
|
offset = 0; |
|
|
|
if (historyStorage === undefined) { |
|
historyStorage = historiesStorage[peerID] = {count: null, history: []}; |
|
} |
|
|
|
|
|
if (maxID > 0) { |
|
for (offset = 0; offset < historyStorage.history.length; offset++) { |
|
if (maxID > historyStorage.history[offset]) { |
|
break; |
|
} |
|
} |
|
} |
|
// dLog('history storage', angular.copy(historyStorage.history), maxID, offset); |
|
|
|
if (historyStorage.count !== null && historyStorage.history.length >= offset + limit) { |
|
return $q.when({ |
|
count: historyStorage.count, |
|
history: historyStorage.history.slice(offset, offset + limit) |
|
}); |
|
} |
|
|
|
var deferred = $q.defer(); |
|
|
|
MtpApiManager.invokeApi('messages.getHistory', { |
|
peer: inputPeer, |
|
offset: offset, |
|
limit: limit, |
|
max_id: 0 |
|
}).then(function (historyResult) { |
|
AppUsersManager.saveApiUsers(historyResult.users); |
|
AppChatsManager.saveApiChats(historyResult.chats); |
|
saveMessages(historyResult.messages); |
|
|
|
historyStorage.count = historyResult._ == 'messages.messagesSlice' |
|
? historyResult.count |
|
: historyResult.messages.length; |
|
|
|
offset = 0; |
|
if (maxID > 0) { |
|
for (offset = 0; offset < historyStorage.history.length; offset++) { |
|
if (maxID > historyStorage.history[offset]) { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
// dLog('history storage after', angular.copy(historyStorage.history), historyResult.messages, maxID, offset); |
|
|
|
historyStorage.history.splice(offset, historyStorage.history.length - offset); |
|
angular.forEach(historyResult.messages, function (message) { |
|
historyStorage.history.push(message.id); |
|
}); |
|
|
|
deferred.resolve({ |
|
count: historyStorage.count, |
|
history: historyStorage.history.slice(offset, offset + limit) |
|
}); |
|
}, function (error) { |
|
deferred.reject(error); |
|
}); |
|
|
|
return deferred.promise; |
|
} |
|
|
|
function processAffectedHistory (inputPeer, affectedHistory) { |
|
if (!ApiUpdatesManager.saveSeq(affectedHistory.seq)) { |
|
return false; |
|
} |
|
if (!affectedHistory.offset) { |
|
return $q.when(); |
|
} |
|
|
|
return MtpApiManager.invokeApi('messages.readHistory', { |
|
peer: inputPeer, |
|
offset: affectedHistory.offset, |
|
max_id: 0 |
|
}).then(function (affectedHistory) { |
|
return processAffectedHistory(inputPeer, affectedHistory); |
|
}); |
|
} |
|
|
|
function readHistory (inputPeer) { |
|
// dLog('start read'); |
|
var peerID = AppPeersManager.getPeerID(inputPeer), |
|
historyStorage = historiesStorage[peerID], |
|
foundDialog = getDialogByPeerID(peerID); |
|
|
|
if (!historyStorage || |
|
!historyStorage.history.length || |
|
foundDialog[0] && !foundDialog[0].unread_count) { |
|
// dLog('bad1'); |
|
return false; |
|
} |
|
|
|
var wasUnread = false; |
|
// dLog(historyStorage); |
|
for (i = 0; i < historyStorage.history.length; i++) { |
|
messageID = historyStorage.history[i]; |
|
message = messagesStorage[messageID]; |
|
// dLog('ms', message); |
|
if (message && !message.out) { |
|
if (message.unread) { |
|
// dLog('unread'); |
|
wasUnread = true; |
|
} else if (!wasUnread) { |
|
// dLog('bad2'); |
|
return false; |
|
} |
|
} |
|
} |
|
|
|
var promise = MtpApiManager.invokeApi('messages.readHistory', { |
|
peer: inputPeer, |
|
offset: 0, |
|
max_id: 0 |
|
}).then(function (affectedHistory) { |
|
return processAffectedHistory(inputPeer, affectedHistory); |
|
}).then(function () { |
|
if (foundDialog[0]) { |
|
foundDialog[0].unread_count = 0; |
|
$rootScope.$broadcast('dialog_unread', {peerID: peerID, count: 0}); |
|
} |
|
}); |
|
|
|
|
|
var messageID, message, i, peerID, foundDialog, dialog; |
|
for (i = 0; i < historyStorage.history.length; i++) { |
|
messageID = historyStorage.history[i]; |
|
message = messagesStorage[messageID]; |
|
if (message && !message.out) { |
|
message.unread = false; |
|
if (messagesForHistory[messageID]) { |
|
messagesForHistory[messageID].unread = false; |
|
} |
|
} |
|
} |
|
|
|
return promise; |
|
} |
|
|
|
function saveMessages (apiMessages) { |
|
angular.forEach(apiMessages, function (apiMessage) { |
|
messagesStorage[apiMessage.id] = apiMessage; |
|
|
|
if (apiMessage.media && apiMessage.media._ == 'messageMediaPhoto') { |
|
AppPhotosManager.savePhoto(apiMessage.media.photo); |
|
} |
|
if (apiMessage.media && apiMessage.media._ == 'messageMediaVideo') { |
|
AppVideoManager.saveVideo(apiMessage.media.video); |
|
} |
|
if (apiMessage.media && apiMessage.media._ == 'messageMediaDocument') { |
|
AppDocsManager.saveDoc(apiMessage.media.document); |
|
} |
|
if (apiMessage.action && apiMessage.action._ == 'messageActionChatEditPhoto') { |
|
AppPhotosManager.savePhoto(apiMessage.action.photo); |
|
} |
|
}); |
|
} |
|
|
|
function getMessagePeer (message) { |
|
var toID = message.to_id && AppPeersManager.getPeerID(message.to_id) || 0; |
|
|
|
if (toID < 0) { |
|
return toID; |
|
} else if (message.out) { |
|
return toID |
|
} |
|
return message.from_id; |
|
} |
|
|
|
function wrapForDialog (msgID, unreadCount) { |
|
var message = angular.copy(messagesStorage[msgID]) || {id: msgID}; |
|
|
|
message.fromUser = AppUsersManager.getUser(message.from_id); |
|
|
|
if (message.chatID = message.to_id.chat_id) { |
|
message.peerID = -message.chatID; |
|
message.peerData = AppChatsManager.getChat(message.chatID); |
|
message.peerString = AppChatsManager.getChatString(message.chatID); |
|
} else { |
|
message.peerID = message.out ? message.to_id.user_id : message.from_id; |
|
message.peerData = AppUsersManager.getUser(message.peerID); |
|
message.peerString = AppUsersManager.getUserString(message.peerID); |
|
} |
|
|
|
message.peerPhoto = AppPeersManager.getPeerPhoto(message.peerID, 'User', 'Group'); |
|
message.unreadCount = unreadCount; |
|
|
|
if (message._ == 'messageService' && message.action.user_id) { |
|
message.action.user = AppUsersManager.getUser(message.action.user_id); |
|
} |
|
|
|
if (message.message && message.message.length) { |
|
message.richMessage = RichTextProcessor.wrapRichText(message.message.substr(0, 64), {noLinks: true, noLinebreaks: true}); |
|
} |
|
|
|
|
|
return message; |
|
} |
|
|
|
function wrapForHistory (msgID) { |
|
if (messagesForHistory[msgID] !== undefined) { |
|
return messagesForHistory[msgID]; |
|
} |
|
|
|
var message = angular.copy(messagesStorage[msgID]) || {id: msgID}; |
|
|
|
message.fromUser = AppUsersManager.getUser(message.from_id); |
|
message.fromPhoto = AppUsersManager.getUserPhoto(message.from_id, 'User'); |
|
|
|
if (message.media) { |
|
switch (message.media._) { |
|
case 'messageMediaPhoto': |
|
message.media.photo = AppPhotosManager.wrapForHistory(message.media.photo.id) |
|
break; |
|
|
|
case 'messageMediaVideo': |
|
message.media.video = AppVideoManager.wrapForHistory(message.media.video.id); |
|
break; |
|
} |
|
|
|
if (message.media.user_id) { |
|
message.media.user = AppUsersManager.getUser(message.media.user_id); |
|
message.media.userPhoto = AppUsersManager.getUserPhoto(message.media.user_id, 'User'); |
|
} |
|
} |
|
else if (message.action) { |
|
if (message.action._ == 'messageActionChatEditPhoto') { |
|
message.action.photo = AppPhotosManager.wrapForHistory(message.action.photo.id); |
|
} |
|
|
|
if (message.action.user_id) { |
|
message.action.user = AppUsersManager.getUser(message.action.user_id); |
|
message.action.userPhoto = AppUsersManager.getUserPhoto(message.action.user_id, 'User'); |
|
} |
|
} |
|
|
|
if (message.message && message.message.length) { |
|
message.richMessage = RichTextProcessor.wrapRichText(message.message); |
|
} |
|
|
|
return messagesForHistory[msgID] = message; |
|
} |
|
|
|
function getDialogByPeerID (peerID) { |
|
for (var i = 0; i < dialogsStorage.dialogs.length; i++) { |
|
if (dialogsStorage.dialogs[i].peerID == peerID) { |
|
return [dialogsStorage.dialogs[i], i]; |
|
} |
|
} |
|
|
|
return []; |
|
} |
|
|
|
|
|
$rootScope.$on('apiUpdate', function (e, update) { |
|
dLog('on apiUpdate', update); |
|
switch (update._) { |
|
case 'updateNewMessage': |
|
var message = update.message, |
|
peerID = getMessagePeer(message), |
|
historyStorage = historiesStorage[peerID]; |
|
|
|
if (historyStorage !== undefined) { |
|
var topMsgID = historiesStorage[peerID].history[0]; |
|
if (message.id <= topMsgID) { |
|
return false; |
|
} |
|
} else { |
|
historyStorage = historiesStorage[peerID] = {count: null, history: []}; |
|
} |
|
|
|
saveMessages([message]); |
|
|
|
if (historyStorage.count !== null) { |
|
historyStorage.count++; |
|
} |
|
historyStorage.history.unshift(message.id); |
|
$rootScope.$broadcast('history_append', {peerID: peerID, messageID: message.id}); |
|
|
|
var foundDialog = getDialogByPeerID(peerID), |
|
dialog; |
|
|
|
if (foundDialog.length) { |
|
dialog = foundDialog[0]; |
|
dialogsStorage.dialogs.splice(foundDialog[1], 1); |
|
} else { |
|
dialog = {peerID: peerID, unread_count: 0, top_message: false} |
|
} |
|
if (!message.out && message.unread) { |
|
dialog.unread_count++; |
|
} |
|
dialog.top_message = message.id; |
|
dialogsStorage.dialogs.unshift(dialog); |
|
$rootScope.$broadcast('dialogs_update', dialog); |
|
break; |
|
|
|
case 'updateReadMessages': |
|
var dialogsUpdated = {}, |
|
messageID, message, i, peerID, foundDialog, dialog; |
|
for (i = 0; i < update.messages.length; i++) { |
|
messageID = update.messages[i]; |
|
message = messagesStorage[messageID]; |
|
// dLog('read', messageID, message); |
|
if (message) { |
|
message.unread = false; |
|
if (messagesForHistory[messageID]) { |
|
messagesForHistory[messageID].unread = false; |
|
} |
|
peerID = getMessagePeer(message); |
|
if (!message.out) { |
|
foundDialog = getDialogByPeerID(peerID); |
|
if (foundDialog) { |
|
dialogsUpdated[peerID] = --foundDialog[0].unread_count; |
|
} |
|
} |
|
} |
|
} |
|
|
|
angular.forEach(dialogsUpdated, function(count, peerID) { |
|
$rootScope.$broadcast('dialog_unread', {peerID: peerID, count: count}); |
|
}); |
|
break; |
|
} |
|
}); |
|
|
|
return { |
|
getDialogs: getDialogs, |
|
getHistory: getHistory, |
|
readHistory: readHistory, |
|
saveMessages: saveMessages, |
|
getMessagePeer: getMessagePeer, |
|
wrapForDialog: wrapForDialog, |
|
wrapForHistory: wrapForHistory |
|
} |
|
}) |
|
|
|
.service('AppPhotosManager', function ($modal, $window, $rootScope, MtpApiFileManager, AppUsersManager) { |
|
var photos = {}; |
|
|
|
function savePhoto (apiPhoto) { |
|
photos[apiPhoto.id] = apiPhoto; |
|
angular.forEach(apiPhoto.sizes, function (photoSize) { |
|
if (photoSize._ == 'photoCachedSize') { |
|
MtpApiFileManager.saveSmallFile(photoSize.location, photoSize.bytes); |
|
|
|
// Memory |
|
photoSize.size = photoSize.bytes.length; |
|
delete photoSize.bytes; |
|
photoSize._ = 'photoSize'; |
|
} |
|
}); |
|
}; |
|
|
|
function choosePhotoSize (photo, width, height) { |
|
var bestPhotoSize = {_: 'photoSizeEmpty'}, |
|
bestDiff = 0xFFFFFF; |
|
|
|
angular.forEach(photo.sizes, function (photoSize) { |
|
var diff = Math.abs(photoSize.w * photoSize.h - width * height); |
|
if (diff < bestDiff) { |
|
bestPhotoSize = photoSize; |
|
bestDiff = diff; |
|
} |
|
}); |
|
|
|
return bestPhotoSize; |
|
} |
|
|
|
function wrapForHistory (photoID) { |
|
var photo = angular.copy(photos[photoID]) || {_: 'photoEmpty'}, |
|
width = 100, |
|
height = 100, |
|
thumbPhotoSize = choosePhotoSize(photo, width, height), |
|
thumb = { |
|
placeholder: 'img/placeholders/PhotoThumbConversation.jpg', |
|
width: width, |
|
height: height |
|
}; |
|
|
|
// dLog('chosen photo size', photoID, thumbPhotoSize); |
|
if (thumbPhotoSize && thumbPhotoSize._ != 'photoSizeEmpty') { |
|
if (thumbPhotoSize.w > thumbPhotoSize.h) { |
|
thumb.height = parseInt(thumbPhotoSize.h * width / thumbPhotoSize.w); |
|
} else { |
|
thumb.width = parseInt(thumbPhotoSize.w * height / thumbPhotoSize.h); |
|
} |
|
|
|
thumb.location = thumbPhotoSize.location; |
|
thumb.size = thumbPhotoSize.size; |
|
} |
|
|
|
photo.thumb = thumb; |
|
|
|
return photo; |
|
} |
|
|
|
function wrapForFull (photoID) { |
|
var photo = wrapForHistory(photoID), |
|
fullWidth = 542, |
|
fullHeight = $($window).height() - 150, |
|
fullPhotoSize = choosePhotoSize(photo, fullWidth, fullHeight), |
|
full = { |
|
placeholder: 'img/placeholders/PhotoThumbModal.jpg', |
|
width: fullWidth, |
|
height: fullHeight |
|
}; |
|
|
|
if (fullPhotoSize && fullPhotoSize._ != 'photoSizeEmpty') { |
|
if (fullPhotoSize.w > fullPhotoSize.h) { |
|
full.height = parseInt(fullPhotoSize.h * fullWidth / fullPhotoSize.w); |
|
} else { |
|
full.width = parseInt(fullPhotoSize.w * fullHeight / fullPhotoSize.h); |
|
if (full.width > fullWidth) { |
|
full.height = parseInt(full.height * fullWidth / full.width); |
|
full.width = fullWidth; |
|
} |
|
} |
|
|
|
full.location = fullPhotoSize.location; |
|
full.size = fullPhotoSize.size; |
|
} |
|
|
|
photo.full = full; |
|
photo.fromUser = AppUsersManager.getUser(photo.user_id); |
|
|
|
return photo; |
|
} |
|
|
|
function openPhoto (photoID, accessHash) { |
|
var scope = $rootScope.$new(true); |
|
scope.photoID = photoID; |
|
|
|
var modalInstance = $modal.open({ |
|
templateUrl: 'partials/photo_modal.html', |
|
controller: 'PhotoModalController', |
|
scope: scope, |
|
backdrop: 'static' |
|
}); |
|
} |
|
|
|
$rootScope.openPhoto = openPhoto; |
|
|
|
|
|
return { |
|
savePhoto: savePhoto, |
|
wrapForHistory: wrapForHistory, |
|
wrapForFull: wrapForFull, |
|
openPhoto: openPhoto |
|
} |
|
}) |
|
|
|
|
|
.service('AppVideoManager', function ($rootScope, $modal, $window, MtpApiFileManager, AppUsersManager) { |
|
var videos = {}; |
|
|
|
function saveVideo (apiVideo) { |
|
videos[apiVideo.id] = apiVideo; |
|
|
|
if (apiVideo.thumb && apiVideo.thumb._ == 'photoCachedSize') { |
|
MtpApiFileManager.saveSmallFile(apiVideo.thumb.location, apiVideo.thumb.bytes); |
|
|
|
// Memory |
|
apiVideo.thumb.size = apiVideo.thumb.bytes.length; |
|
delete apiVideo.thumb.bytes; |
|
apiVideo.thumb._ = 'photoSize'; |
|
} |
|
}; |
|
|
|
function wrapForHistory (videoID) { |
|
var video = angular.copy(videos[videoID]), |
|
width = 100, |
|
height = 100, |
|
thumbPhotoSize = video.thumb, |
|
thumb = { |
|
placeholder: 'img/placeholders/VideoThumbConversation.jpg', |
|
width: width, |
|
height: height |
|
}; |
|
|
|
if (thumbPhotoSize && thumbPhotoSize._ != 'photoSizeEmpty') { |
|
if (thumbPhotoSize.w > thumbPhotoSize.h) { |
|
thumb.height = parseInt(thumbPhotoSize.h * width / thumbPhotoSize.w); |
|
} else { |
|
thumb.width = parseInt(thumbPhotoSize.w * height / thumbPhotoSize.h); |
|
} |
|
|
|
thumb.location = thumbPhotoSize.location; |
|
thumb.size = thumbPhotoSize.size; |
|
} |
|
|
|
video.thumb = thumb; |
|
|
|
return video; |
|
} |
|
|
|
function wrapForFull (videoID) { |
|
var video = wrapForHistory(videoID), |
|
fullWidth = 542, |
|
fullHeight = $($window).height() - 150, |
|
fullPhotoSize = video, |
|
full = { |
|
placeholder: 'img/placeholders/VideoThumbModal.jpg', |
|
width: fullWidth, |
|
height: fullHeight, |
|
}; |
|
|
|
if (video.w > video.h) { |
|
full.height = parseInt(video.h * fullWidth / video.w); |
|
} else { |
|
full.width = parseInt(video.w * fullHeight / video.h); |
|
if (full.width > fullWidth) { |
|
full.height = parseInt(full.height * fullWidth / full.width); |
|
full.width = fullWidth; |
|
} |
|
} |
|
|
|
video.full = full; |
|
video.fromUser = AppUsersManager.getUser(video.user_id); |
|
|
|
return video; |
|
} |
|
|
|
function openVideo (videoID, accessHash) { |
|
var scope = $rootScope.$new(true); |
|
scope.videoID = videoID; |
|
scope.progress = {enabled: false}; |
|
scope.player = {}; |
|
scope.close = function () { |
|
modalInstance.close(); |
|
} |
|
|
|
var modalInstance = $modal.open({ |
|
templateUrl: 'partials/video_modal.html', |
|
controller: 'VideoModalController', |
|
scope: scope |
|
}); |
|
} |
|
|
|
$rootScope.openVideo = openVideo; |
|
|
|
return { |
|
saveVideo: saveVideo, |
|
wrapForHistory: wrapForHistory, |
|
wrapForFull: wrapForFull, |
|
openVideo: openVideo |
|
} |
|
}) |
|
|
|
.service('AppDocsManager', function ($rootScope, $modal, $window, $timeout, MtpApiFileManager, AppUsersManager) { |
|
var docs = {}; |
|
|
|
function saveDoc (apiDoc) { |
|
docs[apiDoc.id] = apiDoc; |
|
|
|
if (apiDoc.thumb && apiDoc.thumb._ == 'photoCachedSize') { |
|
MtpApiFileManager.saveSmallFile(apiDoc.thumb.location, apiDoc.thumb.bytes); |
|
|
|
// Memory |
|
apiDoc.thumb.size = apiDoc.thumb.bytes.length; |
|
delete apiDoc.thumb.bytes; |
|
apiDoc.thumb._ = 'photoSize'; |
|
} |
|
}; |
|
|
|
function wrapForHistory (docID) { |
|
var doc = angular.copy(docs[docID]), |
|
width = 100, |
|
height = 100, |
|
thumbPhotoSize = doc.thumb, |
|
thumb = { |
|
placeholder: 'img/placeholders/DocThumbConversation.jpg', |
|
width: width, |
|
height: height |
|
}; |
|
|
|
if (thumbPhotoSize && thumbPhotoSize._ != 'photoSizeEmpty') { |
|
if (thumbPhotoSize.w > thumbPhotoSize.h) { |
|
thumb.height = parseInt(thumbPhotoSize.h * width / thumbPhotoSize.w); |
|
} else { |
|
thumb.width = parseInt(thumbPhotoSize.w * height / thumbPhotoSize.h); |
|
} |
|
|
|
thumb.location = thumbPhotoSize.location; |
|
thumb.size = thumbPhotoSize.size; |
|
} |
|
|
|
doc.thumb = thumb; |
|
|
|
return doc; |
|
} |
|
|
|
function openDoc (docID, accessHash) { |
|
var doc = docs[docID], |
|
inputFileLocation = { |
|
_: 'inputDocumentFileLocation', |
|
id: docID, |
|
access_hash: accessHash || doc.access_hash |
|
}, |
|
scope = {}; |
|
|
|
scope.progress = {enabled: true, percent: 1}; |
|
|
|
|
|
if (window.chrome && chrome.fileSystem && chrome.fileSystem.chooseEntry) { |
|
var ext = (doc.file_name.split('.', 2) || [])[1] || '', |
|
mime = doc.mime_type; |
|
chrome.fileSystem.chooseEntry({ |
|
type: 'saveFile', |
|
suggestedName: doc.file_name, |
|
accepts: [{ |
|
mimeTypes: [mime], |
|
extensions: [ext] |
|
}] |
|
}, function (writableFileEntry) { |
|
MtpApiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size, writableFileEntry).then(function (url) { |
|
dLog('file save done'); |
|
}); |
|
|
|
}); |
|
} else { |
|
MtpApiFileManager.downloadFile(doc.dc_id, inputFileLocation, doc.size).then(function (url) { |
|
scope.progress.enabled = false; |
|
|
|
var a = $('<a>Download</a>').attr('href', url).attr('target', '_blank').attr('download', doc.file_name).appendTo('body'); |
|
a[0].dataset.downloadurl = ['png', doc.file_name, url].join(':'); |
|
a[0].click(); |
|
$timeout(function () { |
|
a.remove(); |
|
}, 100); |
|
}, function (e) { |
|
dLog('document download failed', e); |
|
}, function (progress) { |
|
scope.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total)); |
|
}); |
|
} |
|
} |
|
|
|
$rootScope.openDoc = openDoc; |
|
|
|
return { |
|
saveDoc: saveDoc, |
|
wrapForHistory: wrapForHistory, |
|
openDoc: openDoc |
|
} |
|
}) |
|
|
|
.service('ExternalResourcesManager', function ($q, $http) { |
|
var urlPromises = {}; |
|
|
|
function downloadImage (url) { |
|
if (urlPromises[url] !== undefined) { |
|
return urlPromises[url]; |
|
} |
|
|
|
var deferred = $q.defer(); |
|
|
|
$http.get(url, {responseType: 'blob', transformRequest: null}) |
|
.then( |
|
function (response) { |
|
deferred.resolve(window.webkitURL.createObjectURL(response.data)); |
|
}, function (error) { |
|
deferred.reject(error); |
|
} |
|
); |
|
|
|
return urlPromises[url] = deferred.promise; |
|
} |
|
|
|
return { |
|
downloadImage: downloadImage |
|
} |
|
}) |
|
|
|
|
|
.service('ApiUpdatesManager', function ($rootScope, MtpNetworkerFactory, AppUsersManager, AppChatsManager, AppPeersManager, MtpApiManager) { |
|
|
|
var curState = {invalid: true}; |
|
|
|
function processUpdateMessage (updateMessage) { |
|
if (curState.invalid) { |
|
return false; |
|
} |
|
|
|
if (updateMessage.seq) { |
|
if (!saveSeq(updateMessage.seq, updateMessage.seq_start)) { |
|
return false; |
|
} |
|
if (updateMessage.date) { |
|
curState.date = updateMessage.date; |
|
} |
|
} |
|
|
|
|
|
switch (updateMessage._) { |
|
case 'updatesTooLong': |
|
getDifference(); |
|
break; |
|
|
|
case 'updateShort': |
|
saveUpdate(updateMessage.update); |
|
break; |
|
|
|
case 'updatesCombined': |
|
case 'updates': |
|
AppUsersManager.saveApiUsers(updateMessage.users); |
|
AppChatsManager.saveApiChats(updateMessage.chats); |
|
|
|
angular.forEach(updateMessage.updates, function (update) { |
|
saveUpdate(update); |
|
}); |
|
break; |
|
|
|
case 'updateShortMessage': |
|
var fromUser = AppUsersManager.getUser(updateMessage.from_id); |
|
if (!fromUser || fromUser.deleted) { |
|
getDifference(); |
|
break; |
|
} |
|
saveUpdate({ |
|
_: 'updateNewMessage', |
|
message: { |
|
_: 'message', |
|
id: updateMessage.id, |
|
from_id: updateMessage.from_id, |
|
to_id: AppPeersManager.getOutputPeer(MtpApiManager.getUserID()), |
|
out: false, |
|
unread: true, |
|
date: updateMessage.date, |
|
message: updateMessage.message, |
|
media: {_: 'messageMediaEmpty'} |
|
}, |
|
pts: updateMessage.pts |
|
}); |
|
break; |
|
|
|
case 'updateShortChatMessage': |
|
var fromUser = AppUsersManager.getUser(updateMessage.from_id), |
|
chat = AppChatsManager.getChat(updateMessage.chat_id); |
|
|
|
if (!fromUser || fromUser.deleted || !chat || chat.deleted) { |
|
getDifference(); |
|
break; |
|
} |
|
saveUpdate({ |
|
_: 'updateNewMessage', |
|
message: { |
|
_: 'message', |
|
id: updateMessage.id, |
|
from_id: updateMessage.from_id, |
|
to_id: AppPeersManager.getOutputPeer(-updateMessage.chat_id), |
|
out: false, |
|
unread: true, |
|
date: updateMessage.date, |
|
message: updateMessage.message, |
|
media: {_: 'messageMediaEmpty'} |
|
}, |
|
pts: updateMessage.pts |
|
}); |
|
break; |
|
} |
|
} |
|
|
|
function getDifference (force) { |
|
if (curState.invalid && !force) { |
|
return false; |
|
} |
|
|
|
curState.invalid = true; |
|
MtpApiManager.invokeApi('updates.getDifference', {pts: curState.pts, date: curState.date, qts: 0}).then(function (differenceResult) { |
|
if (differenceResult._ == 'updates.differenceEmpty') { |
|
curState.date = differenceResult.date; |
|
curState.seq = differenceResult.seq; |
|
delete curState.invalid; |
|
return false; |
|
} |
|
|
|
AppUsersManager.saveApiUsers(differenceResult.users); |
|
AppChatsManager.saveApiChats(differenceResult.chats); |
|
|
|
angular.forEach(differenceResult.new_messages, function (apiMessage) { |
|
saveUpdate({ |
|
_: 'updateNewMessage', |
|
message: apiMessage, |
|
pts: curState.pts |
|
}, true); |
|
}); |
|
|
|
angular.forEach(differenceResult.other_updates, function(update){ |
|
saveUpdate(update, true); |
|
}); |
|
|
|
var nextState = differenceResult.intermediate_state || differenceResult.state; |
|
curState.seq = nextState.seq; |
|
curState.pts = nextState.pts; |
|
curState.date = nextState.date; |
|
|
|
if (differenceResult._ == 'updates.differenceSlice') { |
|
getDifference(true); |
|
} else { |
|
delete curState.invalid; |
|
} |
|
}); |
|
} |
|
|
|
function saveUpdate (update, force) { |
|
if (curState.invalid && !force) { |
|
return false; |
|
} |
|
if (update.pts) { |
|
curState.pts = update.pts; |
|
} |
|
|
|
$rootScope.$broadcast('apiUpdate', update); |
|
} |
|
|
|
function saveSeq (seq, seqStart) { |
|
// dLog('saving seq', curState.invalid, seq, seqStart, curState.seq); |
|
|
|
if (curState.invalid) { |
|
return false; |
|
} |
|
|
|
seqStart = seqStart || seq; |
|
|
|
if (seqStart != curState.seq + 1) { |
|
// dLog('seq hole', seqStart, curState.seq); |
|
if (seqStart != curState.seq) { |
|
getDifference(); |
|
} |
|
return false; |
|
} |
|
|
|
curState.seq = seq; |
|
|
|
return true; |
|
} |
|
|
|
function attach () { |
|
MtpNetworkerFactory.setUpdatesProcessor(processUpdateMessage); |
|
MtpApiManager.invokeApi('updates.getState').then(function (stateResult) { |
|
curState.seq = stateResult.seq; |
|
curState.pts = stateResult.pts; |
|
curState.date = stateResult.date; |
|
delete curState.invalid; |
|
}) |
|
} |
|
|
|
|
|
return { |
|
saveUpdate: saveUpdate, |
|
saveSeq: saveSeq, |
|
attach: attach |
|
} |
|
}) |
|
|
|
.service('RichTextProcessor', function ($sce, $sanitize) { |
|
|
|
var emojiUtf = [], |
|
emojiMap = {}, |
|
emojiData = Config.Emoji, |
|
emojiCode; |
|
|
|
for (emojiCode in emojiData) { |
|
emojiUtf.push(emojiData[emojiCode][0]); |
|
emojiMap[emojiData[emojiCode][0]] = emojiCode; |
|
} |
|
|
|
var regExp = new RegExp('((?:(ftp|https?)://|(?:mailto:)?([A-Za-z0-9._%+-]+@))(\\S*\\.\\S*[^\\s.;,(){}<>"\']))|(\\n)|(' + emojiUtf.join('|') + ')', 'i'); |
|
|
|
// dLog(regExp); |
|
|
|
|
|
return { |
|
wrapRichText: wrapRichText |
|
}; |
|
|
|
function encodeEntities(value) { |
|
return value. |
|
replace(/&/g, '&'). |
|
replace(/([^\#-~| |!])/g, function (value) { // non-alphanumeric |
|
return '&#' + value.charCodeAt(0) + ';'; |
|
}). |
|
replace(/</g, '<'). |
|
replace(/>/g, '>'); |
|
}; |
|
|
|
|
|
function wrapRichText(text, options) { |
|
if (!text || !text.length) { |
|
return ''; |
|
} |
|
|
|
options = options || {}; |
|
|
|
text = text.replace(/\ufe0f/g, '', text); |
|
|
|
var match, |
|
raw = text, |
|
html = [], |
|
url, |
|
emojiTitle, |
|
emojiFound = false; |
|
|
|
|
|
while ((match = raw.match(regExp))) { |
|
// dLog(2, match); |
|
html.push(encodeEntities(raw.substr(0, match.index))); |
|
|
|
if (match[1]) { // URL |
|
if (!options.noLinks) { |
|
if (match[3]) { |
|
html.push( |
|
'<a href="', |
|
encodeEntities('mailto:' + match[3] + match[4]), |
|
'" target="_blank">', |
|
encodeEntities(match[3] + match[4]), |
|
'</a>' |
|
); |
|
} else { |
|
html.push( |
|
'<a href="', |
|
encodeEntities(match[2] + '://' + match[4]), |
|
'" target="_blank">', |
|
encodeEntities((match[2] != 'http' ? match[2] + '://' : '') + match[4]), |
|
'</a>' |
|
); |
|
} |
|
} else { |
|
html.push(encodeEntities(match[0])); |
|
} |
|
} |
|
else if (match[5]) { // New line |
|
if (!options.noLinebreaks) { |
|
html.push('<br/>'); |
|
} else { |
|
html.push(' '); |
|
} |
|
} |
|
else if (match[6]) { |
|
|
|
if (emojiCode = emojiMap[match[6]]) { |
|
emojiFound = true; |
|
emojiTitle = encodeEntities(emojiData[emojiCode][1][0]); |
|
html.push( |
|
'<span class="emoji emoji-file-', |
|
encodeEntities(emojiCode), |
|
'" title="', |
|
emojiTitle, |
|
'">:', |
|
emojiTitle, |
|
':</span>' |
|
); |
|
} else { |
|
html.push(encodeEntities(match[6])); |
|
} |
|
} |
|
raw = raw.substr(match.index + match[0].length); |
|
} |
|
|
|
html.push(encodeEntities(raw)); |
|
|
|
text = $sanitize(html.join('')); |
|
|
|
// dLog(3, text, html); |
|
|
|
if (emojiFound) { |
|
text = text.replace(/<span class="emoji emoji-file-([0-9a-f]+?)"(.+?)<\/span>/g, |
|
'<span class="emoji" style="background-image: url(\'vendor/gemoji/images/$1.png\')"$2</span>'); |
|
} |
|
|
|
// dLog(4, text, html); |
|
|
|
return $sce.trustAs('html', text); |
|
} |
|
|
|
})
|
|
|