After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 574 KiB |
After Width: | Height: | Size: 281 KiB |
After Width: | Height: | Size: 167 B |
After Width: | Height: | Size: 229 B |
After Width: | Height: | Size: 515 B |
After Width: | Height: | Size: 892 B |
After Width: | Height: | Size: 527 B |
After Width: | Height: | Size: 904 B |
After Width: | Height: | Size: 518 B |
After Width: | Height: | Size: 362 B |
After Width: | Height: | Size: 535 B |
After Width: | Height: | Size: 469 B |
After Width: | Height: | Size: 713 B |
After Width: | Height: | Size: 789 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 783 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 529 B |
After Width: | Height: | Size: 476 B |
After Width: | Height: | Size: 875 B |
After Width: | Height: | Size: 474 B |
After Width: | Height: | Size: 898 B |
After Width: | Height: | Size: 574 B |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 4.5 KiB |
After Width: | Height: | Size: 431 B |
After Width: | Height: | Size: 706 B |
After Width: | Height: | Size: 773 B |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 712 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 321 B |
After Width: | Height: | Size: 35 KiB |
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.4 KiB |
@ -0,0 +1,45 @@
|
||||
<!doctype html> |
||||
<html lang="en" ng-app="myApp" ng-csp=""> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
||||
<title>Webogram</title> |
||||
<link rel="stylesheet" href="vendor/angular/angular-csp.css"/> |
||||
<link rel="stylesheet" href="vendor/bootstrap/css/bootstrap.css"/> |
||||
<link rel="stylesheet" href="vendor/jquery.nanoscroller/nanoscroller.css"/> |
||||
<link rel="stylesheet" href="css/app.css"/> |
||||
</head> |
||||
<body> |
||||
|
||||
<div ng-view></div> |
||||
|
||||
<!-- In production use: |
||||
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script> |
||||
--> |
||||
<script type="text/javascript" src="vendor/jquery/jquery.min.js"></script> |
||||
<script type="text/javascript" src="vendor/jquery.nanoscroller/nanoscroller.js"></script> |
||||
<script type="text/javascript" src="vendor/jquery.emojiarea/jquery.emojiarea.js"></script> |
||||
|
||||
<script type="text/javascript" src="vendor/angular/angular.js"></script> |
||||
<script type="text/javascript" src="vendor/angular/angular-route.js"></script> |
||||
<script type="text/javascript" src="vendor/angular/angular-animate.js"></script> |
||||
<script type="text/javascript" src="vendor/angular/angular-sanitize.js"></script> |
||||
<script type="text/javascript" src="vendor/ui-bootstrap/ui-bootstrap-custom-tpls-0.7.0.js?2"></script> |
||||
|
||||
<script type="text/javascript" src="vendor/jsbn/jsbn_combined.js"></script> |
||||
<script type="text/javascript" src="vendor/cryptoJS/crypto.js"></script> |
||||
<script type="text/javascript" src="vendor/zlib/gunzip.min.js"></script> |
||||
|
||||
|
||||
<script type="text/javascript" src="js/lib/config.js"></script> |
||||
<script type="text/javascript" src="js/lib/mtproto.js"></script> |
||||
|
||||
<script type="text/javascript" src="js/util.js"></script> |
||||
<script type="text/javascript" src="js/app.js"></script> |
||||
<script type="text/javascript" src="js/services.js"></script> |
||||
<script type="text/javascript" src="js/controllers.js"></script> |
||||
<script type="text/javascript" src="js/filters.js"></script> |
||||
<script type="text/javascript" src="js/directives.js"></script> |
||||
|
||||
</body> |
||||
</html> |
@ -0,0 +1,55 @@
|
||||
/*! |
||||
* 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'; |
||||
|
||||
// window._testMode = 1;
|
||||
|
||||
|
||||
// Declare app level module which depends on filters, and services
|
||||
angular.module('myApp', [ |
||||
'ngRoute', |
||||
'ngAnimate', |
||||
'ngSanitize', |
||||
'ui.bootstrap', |
||||
'myApp.filters', |
||||
'myApp.services', |
||||
'mtproto.services', |
||||
'myApp.directives', |
||||
'myApp.controllers' |
||||
]). |
||||
config(['$locationProvider', '$routeProvider', '$compileProvider', function($locationProvider, $routeProvider, $compileProvider) { |
||||
|
||||
var icons = {}, reverseIcons = {}, i, j, hex, name, dataItem, |
||||
ranges = [[0x1f600, 0x1f637], [0x270a, 0x270c], [0x1f446, 0x1f450]]; |
||||
|
||||
for (j in ranges) { |
||||
for (i = ranges[j][0]; i <= ranges[j][1]; i++) { |
||||
hex = i.toString(16); |
||||
if (dataItem = Config.Emoji[hex]) { |
||||
name = dataItem[1][0]; |
||||
icons[':' + name + ':'] = hex + '.png'; |
||||
reverseIcons[name] = dataItem[0]; |
||||
} |
||||
} |
||||
} |
||||
|
||||
$.emojiarea.path = 'vendor/gemoji/images'; |
||||
$.emojiarea.icons = icons; |
||||
$.emojiarea.reverseIcons = reverseIcons; |
||||
|
||||
$compileProvider.imgSrcSanitizationWhitelist(/^\s*(https?|ftp|file|blob|filesystem|chrome-extension):|data:image\//); |
||||
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|file|mailto|blob|filesystem|chrome-extension):|data:image\//); |
||||
|
||||
|
||||
// $locationProvider.html5Mode(true);
|
||||
$routeProvider.when('/', {templateUrl: 'partials/welcome.html', controller: 'AppWelcomeController'}); |
||||
$routeProvider.when('/login', {templateUrl: 'partials/login.html', controller: 'AppLoginController'}); |
||||
$routeProvider.when('/im', {templateUrl: 'partials/im.html', controller: 'AppIMController', reloadOnSearch: false}); |
||||
$routeProvider.otherwise({redirectTo: '/'}); |
||||
|
||||
}]); |
@ -0,0 +1,18 @@
|
||||
/*! |
||||
* 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
|
||||
*/ |
||||
|
||||
chrome.app.runtime.onLaunched.addListener(function(launchData) { |
||||
chrome.app.window.create('../index.html', { |
||||
bounds: { |
||||
width: 1100, |
||||
height: 700 |
||||
}, |
||||
minWidth: 1100, |
||||
minHeight: 700, |
||||
frame: 'chrome' |
||||
}); |
||||
}); |
@ -0,0 +1,507 @@
|
||||
/*! |
||||
* 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'; |
||||
|
||||
/* Controllers */ |
||||
|
||||
angular.module('myApp.controllers', []) |
||||
|
||||
.controller('AppWelcomeController', function($scope, $location, MtpApiManager) { |
||||
MtpApiManager.getUserID().then(function (id) { |
||||
if (id) { |
||||
$location.path('/im'); |
||||
} else { |
||||
$location.path('/login'); |
||||
} |
||||
}); |
||||
}) |
||||
|
||||
.controller('AppLoginController', function ($scope, $location, MtpApiManager) { |
||||
var dcID = 1; |
||||
|
||||
$scope.credentials = {}; |
||||
|
||||
function saveAuth (result) { |
||||
MtpApiManager.setUserAuth(dcID, { |
||||
expires: result.expires, |
||||
id: result.user.id |
||||
}); |
||||
|
||||
$location.path('/im'); |
||||
}; |
||||
|
||||
$scope.sendCode = function () { |
||||
MtpApiManager.invokeApi('auth.sendCode', { |
||||
phone_number: $scope.credentials.phone_number, |
||||
sms_type: 0, |
||||
api_id: 2496, |
||||
api_hash: '8da85b0d5bfe62527e5b244c209159c3' |
||||
}, {dcID: dcID}).then(function (sentCode) { |
||||
|
||||
$scope.credentials.phone_code_hash = sentCode.phone_code_hash; |
||||
$scope.credentials.phone_occupied = sentCode.phone_registered; |
||||
$scope.error = {}; |
||||
|
||||
}, function (error) { |
||||
dLog('sendCode', error); |
||||
if (error.code == 303) { |
||||
var newDcID = error.type.match(/^(PHONE_MIGRATE_|NETWORK_MIGRATE_)(\d+)/)[2]; |
||||
if (newDcID != dcID) { |
||||
dcID = newDcID; |
||||
$scope.sendCode(); |
||||
return; |
||||
} |
||||
} |
||||
switch (error.type) { |
||||
case 'PHONE_NUMBER_INVALID': |
||||
$scope.error = {field: 'phone'}; |
||||
break; |
||||
} |
||||
}); |
||||
} |
||||
|
||||
$scope.logIn = function (forceSignUp) { |
||||
var method = 'auth.signIn', params = { |
||||
phone_number: $scope.credentials.phone_number, |
||||
phone_code_hash: $scope.credentials.phone_code_hash, |
||||
phone_code: $scope.credentials.phone_code |
||||
}; |
||||
if (forceSignUp) { |
||||
method = 'auth.signUp'; |
||||
angular.extend(params, { |
||||
first_name: $scope.credentials.first_name, |
||||
last_name: $scope.credentials.last_name |
||||
}); |
||||
} |
||||
|
||||
MtpApiManager.invokeApi(method, params, {dcID: dcID}).then(saveAuth, function (error) { |
||||
if (error.code == 400 && error.type == 'PHONE_NUMBER_UNOCCUPIED') { |
||||
return $scope.logIn(true); |
||||
} else if (error.code == 400 && error.type == 'PHONE_NUMBER_UNOCCUPIED') { |
||||
return $scope.logIn(false); |
||||
} |
||||
|
||||
switch (error.type) { |
||||
case 'FIRSTNAME_INVALID': |
||||
$scope.error = {field: 'first_name'}; |
||||
break; |
||||
case 'LASTNAME_INVALID': |
||||
$scope.error = {field: 'last_name'}; |
||||
break; |
||||
case 'PHONE_CODE_INVALID': |
||||
$scope.error = {field: 'phone_code'}; |
||||
break; |
||||
} |
||||
}); |
||||
|
||||
}; |
||||
}) |
||||
|
||||
.controller('AppIMController', function ($scope, $location, $routeParams, MtpApiManager) { |
||||
|
||||
$scope.$on('$routeUpdate', updateCurDialog); |
||||
|
||||
$scope.isLoggedIn = true; |
||||
$scope.logOut = function () { |
||||
MtpApiManager.logOut().then(function () { |
||||
$location.path('/login'); |
||||
}); |
||||
} |
||||
|
||||
updateCurDialog(); |
||||
|
||||
|
||||
function updateCurDialog() { |
||||
$scope.curDialog = { |
||||
peer: $routeParams.p || false |
||||
}; |
||||
} |
||||
}) |
||||
|
||||
.controller('AppImDialogsController', function ($scope, $location, MtpApiManager, AppUsersManager, AppChatsManager, AppMessagesManager, AppPeersManager) { |
||||
|
||||
$scope.dialogs = []; |
||||
|
||||
var offset = 0, |
||||
hasMore = false, |
||||
limit = 20; |
||||
|
||||
|
||||
MtpApiManager.invokeApi('account.updateStatus', {offline: false}); |
||||
$scope.$on('dialogs_need_more', function () { |
||||
showMoreDialogs(); |
||||
}); |
||||
|
||||
$scope.$on('dialog_unread', function (e, dialog) { |
||||
angular.forEach($scope.dialogs, function(curDialog) { |
||||
if (curDialog.peerID == dialog.peerID) { |
||||
curDialog.unreadCount = dialog.unread_count; |
||||
} |
||||
}); |
||||
}); |
||||
|
||||
$scope.$on('dialogs_update', function (e, dialog) { |
||||
var pos = false; |
||||
angular.forEach($scope.dialogs, function(curDialog, curPos) { |
||||
if (curDialog.peerID == dialog.peerID) { |
||||
pos = curPos; |
||||
} |
||||
}); |
||||
|
||||
var wrappedDialog = AppMessagesManager.wrapForDialog(dialog.top_message, dialog.unread_count); |
||||
if (pos !== false) { |
||||
var prev = $scope.dialogs.splice(pos, 1); |
||||
wrappedDialog = angular.extend(prev[0], wrappedDialog); |
||||
offset++; |
||||
} |
||||
$scope.dialogs.unshift(wrappedDialog); |
||||
}); |
||||
|
||||
loadDialogs(); |
||||
|
||||
|
||||
|
||||
function loadDialogs (startLimit) { |
||||
offset = 0; |
||||
hasMore = false; |
||||
startLimit = startLimit || limit; |
||||
|
||||
AppMessagesManager.getDialogs(offset, startLimit).then(function (dialogsResult) { |
||||
offset += startLimit; |
||||
hasMore = offset < dialogsResult.count; |
||||
|
||||
$scope.dialogs = []; |
||||
angular.forEach(dialogsResult.dialogs, function (dialog) { |
||||
$scope.dialogs.push(AppMessagesManager.wrapForDialog(dialog.top_message, dialog.unread_count)); |
||||
}); |
||||
|
||||
$scope.$broadcast('ui_dialogs_change'); |
||||
}, function (error) { |
||||
if (error.code == 401) { |
||||
$location.path('/login'); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
function showMoreDialogs () { |
||||
if (!hasMore || !offset) { |
||||
return; |
||||
} |
||||
|
||||
AppMessagesManager.getDialogs(offset, limit).then(function (dialogsResult) { |
||||
offset += limit; |
||||
hasMore = offset < dialogsResult.count; |
||||
|
||||
angular.forEach(dialogsResult.dialogs, function (dialog) { |
||||
$scope.dialogs.push(AppMessagesManager.wrapForDialog(dialog.top_message, dialog.unread_count)); |
||||
}); |
||||
|
||||
$scope.$broadcast('ui_dialogs_append'); |
||||
}); |
||||
} |
||||
|
||||
}) |
||||
|
||||
.controller('AppImHistoryController', function ($scope, $location, $timeout, MtpApiManager, AppUsersManager, AppChatsManager, AppMessagesManager, AppPeersManager, ApiUpdatesManager) { |
||||
|
||||
$scope.$watch('curDialog.peer', applyDialogSelect); |
||||
|
||||
ApiUpdatesManager.attach(); |
||||
|
||||
$scope.history = []; |
||||
$scope.typing = {}; |
||||
|
||||
var peerID, offset, hasMore, maxID, limit = 20; |
||||
|
||||
function applyDialogSelect (newPeer) { |
||||
newPeer = newPeer || $scope.curDialog.peer || ''; |
||||
|
||||
peerID = AppPeersManager.getPeerID(newPeer); |
||||
|
||||
$scope.curDialog.peerID = peerID; |
||||
$scope.curDialog.inputPeer = AppPeersManager.getInputPeer(newPeer); |
||||
|
||||
if (peerID) { |
||||
loadHistory(peerID); |
||||
} else { |
||||
showEmptyHistory(); |
||||
} |
||||
} |
||||
|
||||
function showMoreHistory () { |
||||
if (!hasMore || !offset) { |
||||
return; |
||||
} |
||||
|
||||
console.trace('load history'); |
||||
AppMessagesManager.getHistory($scope.curDialog.inputPeer, maxID, limit).then(function (historyResult) { |
||||
offset += limit; |
||||
hasMore = offset < historyResult.count; |
||||
maxID = historyResult.history[historyResult.history.length - 1]; |
||||
|
||||
angular.forEach(historyResult.history, function (id) { |
||||
$scope.history.unshift(AppMessagesManager.wrapForHistory(id)); |
||||
}); |
||||
|
||||
$scope.$broadcast('ui_history_prepend'); |
||||
}, function () { |
||||
$scope.state = {error: true}; |
||||
}); |
||||
} |
||||
|
||||
function loadHistory () { |
||||
hasMore = false; |
||||
offset = 0; |
||||
maxID = 0; |
||||
|
||||
AppMessagesManager.getHistory($scope.curDialog.inputPeer, maxID, limit).then(function (historyResult) { |
||||
offset += limit; |
||||
hasMore = offset < historyResult.count; |
||||
maxID = historyResult.history[historyResult.history.length - 1]; |
||||
|
||||
$scope.history = []; |
||||
angular.forEach(historyResult.history, function (id) { |
||||
$scope.history.push(AppMessagesManager.wrapForHistory(id)); |
||||
}); |
||||
$scope.history.reverse(); |
||||
|
||||
$scope.historyPeer = { |
||||
id: peerID, |
||||
data: AppPeersManager.getPeer(peerID), |
||||
photo: AppPeersManager.getPeerPhoto(peerID, 'User', 'Group') |
||||
}; |
||||
|
||||
$scope.typing = {}; |
||||
|
||||
MtpApiManager.getUserID().then(function (id) { |
||||
$scope.ownPhoto = AppUsersManager.getUserPhoto(id, 'User'); |
||||
}); |
||||
|
||||
$scope.state = {loaded: true}; |
||||
|
||||
$scope.$broadcast('ui_history_change'); |
||||
|
||||
AppMessagesManager.readHistory($scope.curDialog.inputPeer); |
||||
}, function () { |
||||
$scope.state = {error: true}; |
||||
}); |
||||
} |
||||
|
||||
function showEmptyHistory () { |
||||
$scope.state = {notSelected: true}; |
||||
$scope.history = []; |
||||
} |
||||
|
||||
|
||||
|
||||
var typingTimeouts = {}; |
||||
|
||||
$scope.$on('history_append', function (e, addedMessage) { |
||||
if (addedMessage.peerID == $scope.curDialog.peerID) { |
||||
dLog('append', addedMessage); |
||||
// console.trace();
|
||||
$scope.history.push(AppMessagesManager.wrapForHistory(addedMessage.messageID)); |
||||
$scope.typing = {}; |
||||
$scope.$broadcast('ui_history_append'); |
||||
offset++ |
||||
} |
||||
}); |
||||
|
||||
$scope.$on('apiUpdate', function (e, update) { |
||||
// dLog('on apiUpdate inline', update);
|
||||
switch (update._) { |
||||
case 'updateUserTyping': |
||||
if (update.user_id == $scope.curDialog.peerID) { |
||||
$scope.typing = {user: AppUsersManager.getUser(update.user_id)}; |
||||
|
||||
$timeout.cancel(typingTimeouts[update.user_id]); |
||||
|
||||
typingTimeouts[update.user_id] = $timeout(function () { |
||||
$scope.typing = {}; |
||||
}, 6000); |
||||
} |
||||
break; |
||||
|
||||
case 'updateChatUserTyping': |
||||
if (-update.chat_id == $scope.curDialog.peerID) { |
||||
$scope.typing = {user: AppUsersManager.getUser(update.user_id)}; |
||||
|
||||
$timeout.cancel(typingTimeouts[update.user_id]); |
||||
|
||||
typingTimeouts[update.user_id] = $timeout(function () { |
||||
$scope.typing = {}; |
||||
}, 6000); |
||||
} |
||||
break; |
||||
} |
||||
}); |
||||
|
||||
$scope.$on('history_need_more', function () { |
||||
showMoreHistory(); |
||||
}); |
||||
|
||||
}) |
||||
|
||||
.controller('AppImPanelController', function($scope) { |
||||
$scope.$on('user_update', angular.noop); |
||||
}) |
||||
|
||||
.controller('AppImSendController', function ($scope, MtpApiManager, AppPeersManager, AppMessagesManager, ApiUpdatesManager, MtpApiFileManager) { |
||||
|
||||
$scope.$watch('curDialog.peer', resetDraft); |
||||
$scope.$on('user_update', angular.noop); |
||||
|
||||
$scope.draftMessage = {text: ''}; |
||||
|
||||
var lastTyping = false; |
||||
$scope.$watch('draftMessage.text', function (newVal) { |
||||
AppMessagesManager.readHistory($scope.curDialog.inputPeer); |
||||
|
||||
var now = +new Date(); |
||||
if (newVal === undefined || !newVal.length || now - lastTyping < 6000) { |
||||
return; |
||||
} |
||||
lastTyping = now; |
||||
|
||||
MtpApiManager.invokeApi('messages.setTyping', { |
||||
peer: $scope.curDialog.inputPeer, |
||||
typing: true |
||||
}); |
||||
}); |
||||
|
||||
$scope.sendMessage = sendMessage; |
||||
|
||||
$scope.$watch('draftMessage.files', onFilesSelected); |
||||
|
||||
function sendMessage (e) { |
||||
cancelEvent(e); |
||||
|
||||
var text = $scope.draftMessage.text; |
||||
|
||||
if ($scope.draftMessage.sending || !text.length) { |
||||
return false; |
||||
} |
||||
|
||||
text = text.replace(/:\s*(.+?)\s*:/g, function (all, name) { |
||||
var utfChar = $.emojiarea.reverseIcons[name]; |
||||
if (utfChar !== undefined) { |
||||
return utfChar; |
||||
} |
||||
return all; |
||||
}); |
||||
|
||||
$scope.draftMessage.sending = true; |
||||
|
||||
MtpApiManager.invokeApi('messages.sendMessage', { |
||||
peer: $scope.curDialog.inputPeer, |
||||
message: text, |
||||
random_id: $scope.draftMessage.randomID |
||||
}).then(function (result) { |
||||
|
||||
if (ApiUpdatesManager.saveSeq(result.seq)) { |
||||
|
||||
MtpApiManager.getUserID().then(function (fromID) { |
||||
ApiUpdatesManager.saveUpdate({ |
||||
_: 'updateNewMessage', |
||||
message: { |
||||
_: 'message', |
||||
id: result.id, |
||||
from_id: fromID, |
||||
to_id: AppPeersManager.getOutputPeer($scope.curDialog.peerID), |
||||
out: true, |
||||
unread: true, |
||||
date: result.date, |
||||
message: text, |
||||
media: {_: 'messageMediaEmpty'} |
||||
}, |
||||
pts: result.pts |
||||
}); |
||||
}); |
||||
|
||||
} |
||||
|
||||
$scope.$broadcast('ui_message_send'); |
||||
|
||||
resetDraft(); |
||||
}, function () { |
||||
delete $scope.draftMessage.sending; |
||||
}); |
||||
|
||||
return cancelEvent(e); |
||||
} |
||||
|
||||
|
||||
function resetDraft () { |
||||
$scope.draftMessage = { |
||||
randomID: [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)], |
||||
text: '' |
||||
}; |
||||
} |
||||
|
||||
function onFilesSelected (newVal) { |
||||
if (!angular.isArray(newVal) || !newVal.length) { |
||||
return; |
||||
} |
||||
|
||||
for (var i = 0; i < newVal.length; i++) { |
||||
(function (file, randomID) { |
||||
MtpApiFileManager.uploadFile(file).then(function (inputFile) { |
||||
var inputMedia; |
||||
if (file.type == 'image/jpeg') { |
||||
inputMedia = {_: 'inputMediaUploadedPhoto', file: inputFile}; |
||||
} else { |
||||
inputMedia = {_: 'inputMediaUploadedDocument', file: inputFile, file_name: file.name, mime_type: file.type}; |
||||
} |
||||
MtpApiManager.invokeApi('messages.sendMedia', { |
||||
peer: $scope.curDialog.inputPeer, |
||||
media: inputMedia, |
||||
random_id: randomID |
||||
}).then(function (result) { |
||||
|
||||
if (ApiUpdatesManager.saveSeq(result.seq)) { |
||||
ApiUpdatesManager.saveUpdate({ |
||||
_: 'updateNewMessage', |
||||
message: result.message, |
||||
pts: result.pts |
||||
}); |
||||
} |
||||
|
||||
$scope.$broadcast('ui_message_send'); |
||||
}); |
||||
}, function (error) { |
||||
dLog('upload error', error); |
||||
}) |
||||
|
||||
})(newVal[i], [nextRandomInt(0xFFFFFFFF), nextRandomInt(0xFFFFFFFF)]); |
||||
} |
||||
} |
||||
|
||||
}) |
||||
|
||||
.controller('PhotoModalController', function ($scope, AppPhotosManager) { |
||||
$scope.photo = AppPhotosManager.wrapForFull($scope.photoID); |
||||
}) |
||||
|
||||
.controller('VideoModalController', function ($scope, AppVideoManager) { |
||||
$scope.video = AppVideoManager.wrapForFull($scope.videoID); |
||||
}) |
||||
|
||||
.controller('UserModalController', function ($scope, $location, AppUsersManager) { |
||||
$scope.user = AppUsersManager.wrapForFull($scope.userID); |
||||
$scope.goToHistory = function () { |
||||
$scope.$close(); |
||||
$location.url('/im?p=' + $scope.user.peerString); |
||||
}; |
||||
}) |
||||
|
||||
.controller('ChatModalController', function ($scope, AppUsersManager, AppChatsManager, fullChat) { |
||||
$scope.chatFull = AppChatsManager.wrapForFull($scope.chatID, fullChat); |
||||
}) |
||||
|
||||
|
||||
|
@ -0,0 +1,512 @@
|
||||
/*! |
||||
* 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'; |
||||
|
||||
/* Directives */ |
||||
|
||||
|
||||
angular.module('myApp.directives', ['myApp.filters']) |
||||
.directive('myDialog', function() { |
||||
return { |
||||
restrict: 'AE', |
||||
scope: true, |
||||
translude: false, |
||||
templateUrl: 'partials/dialog.html' |
||||
}; |
||||
}) |
||||
|
||||
.directive('myMessage', function() { |
||||
return { |
||||
restrict: 'AE', |
||||
scope: true, |
||||
translude: false, |
||||
templateUrl: 'partials/message.html' |
||||
}; |
||||
}) |
||||
|
||||
.directive('myDialogsList', function($window, $timeout) { |
||||
|
||||
return { |
||||
link: link |
||||
}; |
||||
|
||||
function link (scope, element, attrs) { |
||||
var dialogsWrap = $('.im_dialogs_wrap')[0], |
||||
scrollableWrap = $('.im_dialogs_scrollable_wrap')[0], |
||||
// dialogsSearch = $('im_dialogs_search')[0],
|
||||
moreNotified = false; |
||||
|
||||
onContentLoaded(function () { |
||||
$(dialogsWrap).nanoScroller({preventPageScrolling: true, tabIndex: -1}); |
||||
}); |
||||
|
||||
var updateScroller = function () { |
||||
onContentLoaded(function () { |
||||
$(dialogsWrap).nanoScroller(); |
||||
}); |
||||
} |
||||
|
||||
scope.$on('ui_dialogs_prepend', updateScroller); |
||||
|
||||
|
||||
scope.$on('ui_dialogs_append', function () { |
||||
onContentLoaded(function () { |
||||
updateScroller(); |
||||
moreNotified = false; |
||||
}); |
||||
}); |
||||
|
||||
scope.$on('ui_dialogs_change', function () { |
||||
onContentLoaded(function () { |
||||
updateScroller(); |
||||
moreNotified = false; |
||||
}); |
||||
}); |
||||
|
||||
$(scrollableWrap).on('scroll', function (e) { |
||||
if (!moreNotified && scrollableWrap.scrollTop >= scrollableWrap.scrollHeight - scrollableWrap.clientHeight - 300) { |
||||
scope.$emit('dialogs_need_more'); |
||||
moreNotified = true; |
||||
} |
||||
}); |
||||
|
||||
|
||||
function updateSizes () { |
||||
$(element).css({ |
||||
height: $($window).height() - 162 |
||||
}); |
||||
} |
||||
|
||||
$($window).on('resize', updateSizes); |
||||
|
||||
updateSizes(); |
||||
}; |
||||
|
||||
}) |
||||
|
||||
.directive('myHistory', function ($window, $timeout) { |
||||
|
||||
return { |
||||
link: link |
||||
}; |
||||
|
||||
function link (scope, element, attrs) { |
||||
var historyWrap = $('.im_history_wrap')[0], |
||||
scrollableWrap = $('.im_history_scrollable_wrap')[0], |
||||
scrollable = $('.im_history_scrollable')[0], |
||||
panelWrap = $('.im_history_panel_wrap', element)[0], |
||||
sendFormWrap = $('.im_send_form_wrap', element)[0], |
||||
moreNotified = false; |
||||
|
||||
onContentLoaded(function () { |
||||
$(historyWrap).nanoScroller({preventPageScrolling: true, scroll: 'bottom', tabIndex: -1}); |
||||
}); |
||||
|
||||
var updateScroller = function (delay) { |
||||
$timeout(function () { |
||||
$(historyWrap).nanoScroller(); |
||||
}, delay || 0); |
||||
} |
||||
|
||||
scope.$on('ui_history_append', function () { |
||||
var st = scrollableWrap.scrollTop; |
||||
$(scrollableWrap).addClass('im_history_to_bottom'); |
||||
if (atBottom) { |
||||
onContentLoaded(function () { |
||||
$(scrollableWrap).removeClass('im_history_to_bottom'); |
||||
updateSizes(); |
||||
$(historyWrap).nanoScroller({scrollBottom: 0}); |
||||
// scrollableWrap.scrollTop = st;
|
||||
// $(scrollableWrap).animate({
|
||||
// scrollTop: scrollableWrap.scrollHeight - scrollableWrap.clientHeight
|
||||
// }, 200);
|
||||
updateScroller(); |
||||
}); |
||||
} |
||||
}); |
||||
|
||||
scope.$on('ui_history_change', function () { |
||||
$(scrollableWrap).addClass('im_history_to_bottom'); |
||||
$(scrollable).css({bottom: 0}); |
||||
onContentLoaded(function () { |
||||
$(scrollableWrap).removeClass('im_history_to_bottom'); |
||||
$(scrollable).css({bottom: ''}); |
||||
updateSizes(); |
||||
$(historyWrap).nanoScroller(); |
||||
$(historyWrap).nanoScroller({scrollBottom: 0}); |
||||
updateScroller(100); |
||||
moreNotified = false; |
||||
}); |
||||
}); |
||||
|
||||
|
||||
scope.$on('ui_history_prepend', function () { |
||||
var sh = scrollableWrap.scrollHeight, |
||||
st = scrollableWrap.scrollTop, |
||||
ch = scrollableWrap.clientHeight; |
||||
|
||||
$(scrollableWrap).addClass('im_history_to_bottom'); |
||||
$(scrollable).css({bottom: -(sh - st - ch)}); |
||||
|
||||
onContentLoaded(function () { |
||||
$(scrollableWrap).removeClass('im_history_to_bottom'); |
||||
$(scrollable).css({bottom: ''}); |
||||
$(historyWrap).nanoScroller(); |
||||
$(historyWrap).nanoScroller({scrollTop: st + scrollableWrap.scrollHeight - sh}); |
||||
|
||||
// updateScroller();
|
||||
updateScroller(50); |
||||
moreNotified = false; |
||||
}); |
||||
}); |
||||
|
||||
var atBottom = true; |
||||
$(scrollableWrap).on('scroll', function (e) { |
||||
atBottom = scrollableWrap.scrollTop >= scrollableWrap.scrollHeight - scrollableWrap.clientHeight; |
||||
|
||||
if (!moreNotified && scrollableWrap.scrollTop <= 300) { |
||||
moreNotified = true; |
||||
scope.$emit('history_need_more'); |
||||
} |
||||
}); |
||||
|
||||
function updateSizes () { |
||||
$(historyWrap).css({ |
||||
height: $($window).height() - panelWrap.offsetHeight - sendFormWrap.offsetHeight - 90 |
||||
}); |
||||
if (atBottom) { |
||||
onContentLoaded(function () { |
||||
$(historyWrap).nanoScroller({scroll: 'bottom'}); |
||||
}); |
||||
} |
||||
updateScroller(100); |
||||
} |
||||
|
||||
$($window).on('resize', updateSizes); |
||||
|
||||
onContentLoaded(updateSizes); |
||||
} |
||||
|
||||
}) |
||||
|
||||
.directive('mySendForm', function ($timeout) { |
||||
|
||||
return { |
||||
link: link, |
||||
scope: { |
||||
draftMessage: '=' |
||||
} |
||||
}; |
||||
|
||||
function link (scope, element, attrs) { |
||||
var messageField = $('textarea', element)[0], |
||||
fileSelect = $('input', element)[0], |
||||
dropbox = $('.im_send_dropbox_wrap', element)[0], |
||||
emojiButton = $('.im_emoji_btn', element)[0], |
||||
editorElement = messageField, |
||||
dragStarted, dragTimeout, |
||||
emojiArea = $(messageField).emojiarea({button: emojiButton}), |
||||
emojiMenu = $('.emoji-menu')[0], |
||||
richTextarea = $('.emoji-wysiwyg-editor', element)[0]; |
||||
|
||||
if (richTextarea) { |
||||
editorElement = richTextarea; |
||||
$(richTextarea).addClass('form-control'); |
||||
$(richTextarea).attr('placeholder', $(messageField).attr('placeholder')); |
||||
} |
||||
|
||||
// $(emojiMenu.firstChild).addClass('nano').nanoScroller({preventPageScrolling: true, tabIndex: -1});
|
||||
|
||||
|
||||
$(fileSelect).on('change', function () { |
||||
scope.$apply(function () { |
||||
scope.draftMessage.files = Array.prototype.slice.call(fileSelect.files); |
||||
}); |
||||
}); |
||||
|
||||
var sendOnEnter = true; |
||||
$(editorElement).on('keydown', function (e) { |
||||
if (e.keyCode != 13) { |
||||
return; |
||||
} |
||||
var submit = false; |
||||
if (sendOnEnter && !e.shiftKey) { |
||||
submit = true; |
||||
} else if (!sendOnEnter && (e.ctrlKey || e.metaKey)) { |
||||
submit = true; |
||||
} |
||||
|
||||
if (submit) { |
||||
$(element).trigger('submit'); |
||||
dLog('after submit'); |
||||
return cancelEvent(e); |
||||
} |
||||
}); |
||||
|
||||
if (richTextarea) { |
||||
scope.$watch('draftMessage.text', function (newVal) { |
||||
if (!newVal.length && !messageField.value.length) { |
||||
$timeout(function () { |
||||
$(richTextarea).html(''); |
||||
}, 0); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
|
||||
$('body').on('dragenter dragleave dragover drop', onDragDropEvent); |
||||
|
||||
scope.$on('ui_history_change', focusField); |
||||
scope.$on('ui_message_send', focusField); |
||||
|
||||
scope.$on('$destroy', function cleanup() { |
||||
$('body').off('dragenter dragleave dragover drop', onDragDropEvent); |
||||
}); |
||||
|
||||
focusField(); |
||||
|
||||
function focusField () { |
||||
onContentLoaded(function () { |
||||
$(editorElement).focus(); |
||||
}); |
||||
} |
||||
|
||||
function onDragDropEvent(e) { |
||||
var dragStateChanged = false; |
||||
if (!dragStarted || dragStarted == 1) { |
||||
dragStarted = checkDragEvent(e) ? 2 : 1; |
||||
dragStateChanged = true; |
||||
} |
||||
if (dragStarted == 2) { |
||||
if (dragTimeout) { |
||||
setTimeout(function () { |
||||
clearTimeout(dragTimeout); |
||||
dragTimeout = false; |
||||
}, 0); |
||||
} |
||||
|
||||
if (e.type == 'dragenter' || e.type == 'dragover') { |
||||
if (dragStateChanged) { |
||||
$(dropbox) |
||||
.css({height: $(editorElement).height() + 12, width: $(editorElement).width() + 12}) |
||||
.show(); |
||||
} |
||||
} else { |
||||
if (e.type == 'drop') { |
||||
scope.$apply(function () { |
||||
scope.draftMessage.files = Array.prototype.slice.call(e.originalEvent.dataTransfer.files); |
||||
}); |
||||
} |
||||
dragTimeout = setTimeout(function () { |
||||
$(dropbox).hide(); |
||||
dragStarted = false; |
||||
dragTimeout = false; |
||||
}, 300); |
||||
} |
||||
} |
||||
|
||||
return cancelEvent(e); |
||||
}; |
||||
} |
||||
|
||||
}) |
||||
|
||||
.directive('myLoadThumb', function(MtpApiFileManager) { |
||||
|
||||
return { |
||||
link: link, |
||||
scope: { |
||||
thumb: '=' |
||||
} |
||||
}; |
||||
|
||||
function link (scope, element, attrs) { |
||||
|
||||
scope.$watch('thumb.location', function (newVal) { |
||||
if (!scope.thumb) dLog(222, scope); |
||||
if (!scope.thumb.location) { |
||||
element.attr('src', scope.thumb.placeholder || ''); |
||||
return; |
||||
} |
||||
|
||||
MtpApiFileManager.downloadSmallFile(scope.thumb.location, scope.thumb.size).then(function (url) { |
||||
element.attr('src', url); |
||||
}, function (e) { |
||||
dLog('Download image failed', e, scope.thumb.location); |
||||
element.attr('src', scope.thumb.placeholder || ''); |
||||
}); |
||||
}) |
||||
|
||||
} |
||||
|
||||
}) |
||||
|
||||
.directive('myLoadFullPhoto', function(MtpApiFileManager) { |
||||
|
||||
return { |
||||
link: link, |
||||
transclude: true, |
||||
template: |
||||
'<div class="img_fullsize_with_progress_wrap" ng-style="{width: fullPhoto.width + \'px\', height: fullPhoto.height + \'px\'}">\ |
||||
<div class="img_fullsize_progress_overlay" ng-show="progress.enabled">\ |
||||
<div class="img_fullsize_progress_wrap" ng-style="{width: fullPhoto.width + \'px\', height: fullPhoto.height + \'px\'}">\ |
||||
<div class="img_fullsize_progress progress tg_progress">\ |
||||
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="{{progress.percent}}" aria-valuemin="0" aria-valuemax="100" style="width: {{progress.percent}}%">\ |
||||
<span class="sr-only">{{progress.percent}}% Complete (success)</span>\ |
||||
</div>\ |
||||
</div>\ |
||||
</div>\ |
||||
</div>\ |
||||
<div class="photo_full_wrap">\ |
||||
<a class="photo_modal_image">\ |
||||
<img class="photo_modal_image" width="{{fullPhoto.width}}" height="{{fullPhoto.height}}" />\ |
||||
</a>\ |
||||
</div>\ |
||||
</div>', |
||||
scope: { |
||||
fullPhoto: '=', |
||||
thumbLocation: '=' |
||||
} |
||||
}; |
||||
|
||||
function link (scope, element, attrs) { |
||||
var imgElement = $('img', element), |
||||
fullLoaded = false; |
||||
|
||||
|
||||
if (!scope.fullPhoto.location) { |
||||
imgElement.attr('src', scope.fullPhoto.placeholder || ''); |
||||
return; |
||||
} |
||||
|
||||
MtpApiFileManager.getCachedFile(scope.thumbLocation).then(function (url) { |
||||
if (!fullLoaded) { |
||||
imgElement |
||||
.attr('src', url) |
||||
.addClass('thumb_blurred') |
||||
.addClass('thumb_blur_animation'); |
||||
} |
||||
}); |
||||
|
||||
var apiPromise; |
||||
if (scope.fullPhoto.size) { |
||||
var inputLocation = { |
||||
_: 'inputFileLocation', |
||||
volume_id: scope.fullPhoto.location.volume_id, |
||||
local_id: scope.fullPhoto.location.local_id, |
||||
secret: scope.fullPhoto.location.secret |
||||
}; |
||||
apiPromise = MtpApiFileManager.downloadFile(scope.fullPhoto.location.dc_id, inputLocation, scope.fullPhoto.size); |
||||
} else { |
||||
apiPromise = MtpApiFileManager.downloadSmallFile(scope.fullPhoto.location); |
||||
} |
||||
|
||||
scope.progress = {enabled: true, percent: 1}; |
||||
|
||||
apiPromise.then(function (url) { |
||||
fullLoaded = true; |
||||
scope.progress.enabled = false; |
||||
imgElement |
||||
.attr('src', url) |
||||
.removeClass('thumb_blurred'); |
||||
|
||||
}, function (e) { |
||||
dLog('Download image failed', e, scope.fullPhoto.location); |
||||
scope.progress.enabled = false; |
||||
imgElement |
||||
.attr('src', scope.fullPhoto.placeholder || '') |
||||
.removeClass('thumb_blurred'); |
||||
|
||||
}, function (progress) { |
||||
scope.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total)); |
||||
}); |
||||
} |
||||
|
||||
}) |
||||
|
||||
|
||||
.directive('myLoadVideo', function($sce, MtpApiFileManager) { |
||||
|
||||
return { |
||||
link: link, |
||||
transclude: true, |
||||
template: |
||||
'<div class="img_fullsize_with_progress_wrap" ng-style="{width: video.full.width + \'px\', height: video.full.height + \'px\'}">\ |
||||
<div class="img_fullsize_progress_overlay" ng-show="progress.enabled">\ |
||||
<div class="img_fullsize_progress_wrap" ng-style="{width: video.full.width + \'px\', height: video.full.height + \'px\'}">\ |
||||
<div class="img_fullsize_progress progress tg_progress">\ |
||||
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuenow="{{progress.percent}}" aria-valuemin="0" aria-valuemax="100" style="width: {{progress.percent}}%">\ |
||||
<span class="sr-only">{{progress.percent}}% Complete (success)</span>\ |
||||
</div>\ |
||||
</div>\ |
||||
</div>\ |
||||
</div>\ |
||||
<div class="img_fullsize_wrap" ng-if="!player.src">\ |
||||
<img class="img_fullsize" my-load-thumb thumb="video.thumb" width="{{video.full.width}}" height="{{video.full.height}}" />\ |
||||
</div>\ |
||||
<div class="video_full_player" ng-if="player.src">\ |
||||
<video width="{{video.full.width}}" height="{{video.full.height}}" controls autoplay>\ |
||||
<source ng-src="{{player.src}}" type="video/mp4">\ |
||||
</video>\ |
||||
</div>\ |
||||
</div>', |
||||
scope: { |
||||
video: '=' |
||||
} |
||||
}; |
||||
|
||||
function link (scope, element, attrs) { |
||||
|
||||
scope.progress = {enabled: true, percent: 1}; |
||||
scope.player = {}; |
||||
|
||||
var inputLocation = { |
||||
_: 'inputVideoFileLocation', |
||||
id: scope.video.id, |
||||
access_hash: scope.video.access_hash |
||||
}; |
||||
|
||||
MtpApiFileManager.downloadFile(scope.video.dc_id, inputLocation, scope.video.size).then(function (url) { |
||||
scope.progress.enabled = false; |
||||
// scope.progress = {enabled: true, percent: 50};
|
||||
scope.player.src = $sce.trustAsResourceUrl(url); |
||||
}, function (e) { |
||||
dLog('Download image failed', e, scope.fullPhoto.location); |
||||
scope.progress.enabled = false; |
||||
scope.player.src = ''; |
||||
}, function (progress) { |
||||
scope.progress.percent = Math.max(1, Math.floor(100 * progress.done / progress.total)); |
||||
}); |
||||
} |
||||
|
||||
}) |
||||
|
||||
.directive('myMapPoint', function(ExternalResourcesManager) { |
||||
|
||||
return { |
||||
link: link, |
||||
scope: { |
||||
point: '=' |
||||
} |
||||
}; |
||||
|
||||
function link (scope, element, attrs) { |
||||
|
||||
var apiKey = 'AIzaSyC32ij28dCa0YzEV_HqbWfIwTZQql-RNS0'; |
||||
|
||||
var src = 'https://maps.googleapis.com/maps/api/staticmap?sensor=false¢er=' + scope.point['lat'] + ',' + scope.point['long'] + '&zoom=13&size=200x100&scale=2&key=' + apiKey; |
||||
|
||||
ExternalResourcesManager.downloadImage(src).then(function (url) { |
||||
element.append('<img src="' + url + '" width="200" height="100"/>'); |
||||
}); |
||||
|
||||
element.attr('href','https://maps.google.com/?q=' + scope.point['lat'] + ',' + scope.point['long']); |
||||
element.attr('target','_blank'); |
||||
} |
||||
|
||||
}) |
@ -0,0 +1,126 @@
|
||||
/*! |
||||
* 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'; |
||||
|
||||
/* Filters */ |
||||
|
||||
angular.module('myApp.filters', []) |
||||
|
||||
.filter('userName', [function() { |
||||
return function (user) { |
||||
if (!user || !user.first_name && !user.last_name) { |
||||
return 'DELETED'; |
||||
} |
||||
return user.first_name + ' ' + user.last_name; |
||||
} |
||||
}]) |
||||
|
||||
.filter('userFirstName', [function() { |
||||
return function (user) { |
||||
if (!user || !user.first_name && !user.last_name) { |
||||
return 'DELETED'; |
||||
} |
||||
return user.first_name || user.last_name; |
||||
} |
||||
}]) |
||||
|
||||
.filter('userStatus', ['$filter', function($filter) { |
||||
return function (user) { |
||||
if (!user || !user.status || user.status._ == 'userStatusEmpty') { |
||||
return 'offline'; |
||||
} |
||||
if (user.status._ == 'userStatusOnline') { |
||||
return 'online'; |
||||
} |
||||
|
||||
return 'last seen ' + $filter('relativeTime')(user.status.was_online); |
||||
} |
||||
}]) |
||||
|
||||
.filter('chatTitle', [function() { |
||||
return function (chat) { |
||||
if (!chat || !chat.title) { |
||||
return 'DELETED'; |
||||
} |
||||
return chat.title; |
||||
} |
||||
}]) |
||||
|
||||
.filter('dateOrTime', ['$filter', function($filter) { |
||||
return function (timestamp) { |
||||
var ticks = timestamp * 1000, |
||||
diff = Math.abs(+new Date() - ticks), |
||||
format = 'HH:mm'; |
||||
|
||||
if (diff > 518400000) { // 6 days
|
||||
format = 'shortDate'; |
||||
} |
||||
else if (diff > 43200000) { // 12 hours
|
||||
format = 'EEE'; |
||||
} |
||||
return $filter('date')(ticks, format); |
||||
} |
||||
}]) |
||||
|
||||
.filter('duration', [function() { |
||||
return function (duration) { |
||||
var secs = duration % 60, |
||||
mins = Math.floor((duration - secs) / 60.0); |
||||
|
||||
if (secs < 10) { |
||||
secs = '0' + secs; |
||||
} |
||||
|
||||
return mins + ':' + secs; |
||||
} |
||||
}]) |
||||
|
||||
.filter('phoneNumber', [function() { |
||||
return function (phoneRaw) { |
||||
if (phoneRaw.charAt(0) == '7') { |
||||
return '+' + phoneRaw.charAt(0) + ' (' + phoneRaw.substr(1, 3) + ') ' + phoneRaw.substr(4, 3) + '-' + phoneRaw.substr(7, 2) + '-' + phoneRaw.substr(9, 2); |
||||
} |
||||
return '+' + phoneRaw; |
||||
} |
||||
}]) |
||||
|
||||
.filter('formatSize', [function () { |
||||
return function (size) { |
||||
return Math.round(size / 1024) + 'Kb'; |
||||
} |
||||
}]) |
||||
|
||||
.filter('nl2br', [function () { |
||||
return function (text) { |
||||
return text.replace(/\n/g, '<br/>'); |
||||
} |
||||
}]) |
||||
|
||||
.filter('richText', ['$filter', function ($filter) { |
||||
return function (text) { |
||||
return $filter('linky')(text, '_blank').replace(/\n| /g, '<br/>'); |
||||
} |
||||
}]) |
||||
|
||||
.filter('relativeTime', ['$filter', function($filter) { |
||||
return function (timestamp) { |
||||
var ticks = timestamp * 1000, |
||||
diff = Math.abs(+new Date() - ticks); |
||||
|
||||
if (diff < 60000) { |
||||
return 'just now'; |
||||
} |
||||
if (diff < 3000000) { |
||||
return Math.ceil(diff / 60000) + ' minutes ago'; |
||||
} |
||||
if (diff < 10000000) { |
||||
return Math.ceil(diff / 3600000) + ' hours ago'; |
||||
} |
||||
return $filter('dateOrTime')(timestamp); |
||||
} |
||||
}]) |
@ -0,0 +1,21 @@
|
||||
/*! |
||||
* 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
|
||||
*/ |
||||
|
||||
importScripts('mtproto.js', '../../vendor/jsbn/jsbn_combined.js', '../../vendor/cryptoJS/crypto.js'); |
||||
|
||||
onmessage = function (e) { |
||||
// console.log('AES worker in', e.data);
|
||||
var taskID = e.data.taskID, |
||||
result; |
||||
|
||||
if (e.data.task == 'encrypt') { |
||||
result = aesEncrypt(e.data.bytes, e.data.keyBytes, e.data.ivBytes); |
||||
} else { |
||||
result = aesDecrypt(e.data.encryptedBytes, e.data.keyBytes, e.data.ivBytes); |
||||
} |
||||
postMessage({taskID: taskID, result: result}); |
||||
} |
@ -0,0 +1,12 @@
|
||||
/*! |
||||
* 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
|
||||
*/ |
||||
|
||||
importScripts('mtproto.js', 'jsbn.js'); |
||||
|
||||
onmessage = function (e) { |
||||
postMessage(pqPrimeFactorization(e.data)); |
||||
} |
@ -0,0 +1,14 @@
|
||||
/*! |
||||
* 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
|
||||
*/ |
||||
|
||||
importScripts('mtproto.js', '../../vendor/cryptoJS/crypto.js'); |
||||
|
||||
onmessage = function (e) { |
||||
var taskID = e.data.taskID; |
||||
|
||||
postMessage({taskID: taskID, result: sha1Hash(e.data.bytes)}); |
||||
} |
@ -0,0 +1,55 @@
|
||||
/*! |
||||
* 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
|
||||
*/ |
||||
|
||||
function checkClick (e, noprevent) { |
||||
if (e.which == 1 && (e.ctrlKey || e.metaKey) || e.which == 2) { |
||||
return true; |
||||
} |
||||
|
||||
if (!noprevent) { |
||||
e.preventDefault(); |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
function checkDragEvent(e) { |
||||
if (!e || e.target && (e.target.tagName == 'IMG' || e.target.tagName == 'A')) return false; |
||||
if (e.dataTransfer && e.dataTransfer.types) { |
||||
for (var i = 0; i < e.dataTransfer.types.length; i++) { |
||||
if (e.dataTransfer.types[i] == 'Files') { |
||||
return true; |
||||
} |
||||
} |
||||
} else { |
||||
return true; |
||||
} |
||||
|
||||
return false; |
||||
} |
||||
|
||||
function cancelEvent (event) { |
||||
event = event || window.event; |
||||
|
||||
event.stopPropagation && event.stopPropagation(); |
||||
event.preventDefault && event.preventDefault(); |
||||
|
||||
return false; |
||||
} |
||||
|
||||
function onCtrlEnter (textarea, cb) { |
||||
$(textarea).on('keydown', function (e) { |
||||
if (e.keyCode == 13 && (e.ctrlKey || e.metaKey)) { |
||||
cb(); |
||||
return cancelEvent(e); |
||||
} |
||||
}); |
||||
} |
||||
|
||||
function onContentLoaded (cb) { |
||||
setTimeout(cb, 0); |
||||
}; |
@ -0,0 +1,18 @@
|
||||
{ |
||||
"name": "Telegram UNOFFICIAL", |
||||
"version": "0.0.9", |
||||
"short_name": "Webogram", |
||||
"manifest_version": 2, |
||||
"app": { |
||||
"background": { |
||||
"scripts": ["vendor/angular/angular.js", "js/background.js"] |
||||
} |
||||
}, |
||||
"permissions": [ |
||||
"webview", |
||||
{"fileSystem": ["write"]}, |
||||
"storage" |
||||
], |
||||
"icons": { "16": "img/icons/icon16.png", |
||||
"128": "img/icons/icon128.png" } |
||||
} |
@ -0,0 +1,41 @@
|
||||
<div class="chat_modal_wrap"> |
||||
|
||||
<div class="modal-header"> |
||||
<!-- <a class="modal-close-link" ng-click="$close()">Close</a> --> |
||||
<h4 class="modal-title">Group Info</h4> |
||||
</div> |
||||
|
||||
<div class="modal-body"> |
||||
|
||||
<div class="chat_modal_image_wrap pull-left"> |
||||
<img class="chat_modal_image" my-load-thumb thumb="chatFull.thumb"/> |
||||
</div> |
||||
|
||||
<div class="chat_modal_info_wrap clearfix"> |
||||
<h4 class="chat_modal_header">{{chatFull.chat | chatTitle}}</h4> |
||||
<p class="chat_modal_members_count" ng-if="chatFull.participants._ == 'chatParticipants'"> |
||||
<ng-pluralize count="chatFull.participants.participants.length" |
||||
when="{'0': 'No members', 'one': '1 member', 'other': '{} members'}"> |
||||
</ng-pluralize> |
||||
</p> |
||||
|
||||
<button class="btn btn-link chat_modal_invite_btn">Add member</button> |
||||
</div> |
||||
|
||||
<h5 class="chat_modal_members_header">Members</h5> |
||||
<div class="chat_modal_members_list"> |
||||
|
||||
<div class="chat_modal_participant_wrap clearfix" ng-repeat="participant in chatFull.participants.participants"> |
||||
<a ng-click="openUser(participant.user_id)" class="chat_modal_participant_photo pull-left"> |
||||
<img class="chat_modal_participant_photo" my-load-thumb thumb="participant.userPhoto"/> |
||||
</a> |
||||
<div class="chat_modal_participant_name">{{participant.user | userName}}</div> |
||||
<!--div class="chat_modal_participant_desc">invited by {{participant.inviter | userName}} {{participant.date | dateOrTime}}</div--> |
||||
<div class="chat_modal_participant_status">{{participant.user | userStatus}}</div> |
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
</div> |
@ -0,0 +1,73 @@
|
||||
<a class="im_dialog clearfix" href="#/im?p={{dialogMessage.peerString}}"> |
||||
|
||||
<div class="im_dialog_meta pull-right text-right"> |
||||
<div class="im_dialog_date"> |
||||
{{dialogMessage.date | dateOrTime}} |
||||
</div> |
||||
<span class="im_dialog_badge badge" ng-show="dialogMessage.unreadCount > 0"> |
||||
{{dialogMessage.unreadCount}} |
||||
</span> |
||||
</div> |
||||
|
||||
<div class="im_dialog_photo pull-left"> |
||||
<img class="im_dialog_photo" my-load-thumb thumb="dialogMessage.peerPhoto" aaa="{{dialogMessage.peerPhoto.location}}" /> |
||||
</div> |
||||
|
||||
<div class="im_dialog_message_wrap"> |
||||
|
||||
<div class="im_dialog_peer"> |
||||
<span class="im_dialog_chat" ng-if="dialogMessage.chatID"> |
||||
<i class="icon icon-group"></i> <span ng-bind-html="dialogMessage.peerData.rTitle"></span> |
||||
</span> |
||||
<span class="im_dialog_user" ng-if="dialogMessage.peerID > 0" ng-bind-html="dialogMessage.peerData.rFullName"></span> |
||||
</div> |
||||
|
||||
<div class="im_dialog_message"> |
||||
<span class="im_dialog_chat_from_wrap"> |
||||
<span class="im_dialog_chat_from" ng-if="!dialogMessage.out && dialogMessage.chatID" ng-bind-html="dialogMessage.fromUser.rFirstName"></span><span class="im_dialog_chat_from" ng-if="dialogMessage.out">You</span>{{((dialogMessage.out || dialogMessage.peerID < 0) && (dialogMessage.message.length || dialogMessage.media && dialogMessage.media._ != 'messageMediaEmpty')) ? ':' : ''}} |
||||
</span> |
||||
|
||||
<span class="im_dialog_message_media" ng-if="dialogMessage.media && dialogMessage.media._ != 'messageMediaEmpty'" ng-switch="dialogMessage.media._"> |
||||
<span ng-switch-when="messageMediaPhoto"> |
||||
<span class="glyphicon glyphicon-camera"></span> Photo |
||||
</span> |
||||
<span ng-switch-when="messageMediaVideo"> |
||||
<span class="glyphicon glyphicon-facetime-video"></span> Video |
||||
</span> |
||||
<span ng-switch-when="messageMediaDocument"> |
||||
<span class="glyphicon glyphicon-document"></span> Document |
||||
</span> |
||||
<span ng-switch-when="messageMediaGeo"> |
||||
<span class="glyphicon glyphicon-map-marker"></span> Location |
||||
</span> |
||||
<span ng-switch-when="messageMediaContact"> |
||||
<span class="glyphicon glyphicon-user"></span> Contact |
||||
</span> |
||||
</span> |
||||
|
||||
<span class="im_dialog_message_service" ng-if="dialogMessage._ == 'messageService'" ng-switch="dialogMessage.action._"> |
||||
<span ng-switch-when="messageActionChatCreate"> |
||||
created the group |
||||
</span> |
||||
<span ng-switch-when="messageActionChatEditTitle"> |
||||
changed group name |
||||
</span> |
||||
<span ng-switch-when="messageActionChatEditPhoto"> |
||||
changed group photo |
||||
</span> |
||||
<span ng-switch-when="messageActionChatDeletePhoto"> |
||||
removed group photo |
||||
</span> |
||||
<span ng-switch-when="messageActionChatAddUser"> |
||||
invited <span ng-bind-html="dialogMessage.action.user.rFullName"></span> |
||||
</span> |
||||
<span ng-switch-when="messageActionChatDeleteUser"> |
||||
kicked <span ng-bind-html="dialogMessage.action.user.rFullName"></span> |
||||
</span> |
||||
</span> |
||||
|
||||
<span class="im_dialog_message_text" ng-if="dialogMessage.message" ng-bind-html="dialogMessage.richMessage"></span> |
||||
</div> |
||||
|
||||
</div> |
||||
</a> |
@ -0,0 +1,17 @@
|
||||
<div class="tg_page_head"> |
||||
<div class="navbar navbar-static-top navbar-inverse" role="navigation"> |
||||
<div class="container"> |
||||
<div class="navbar-header"> |
||||
<a class="navbar-brand" href="https://zhukov.github.io/webogram"><img src="img/Logo_2x.png" class="tg_head_logo" alt="Telegram logo" width="110" height="31" /> <span class="navbar-brand-alpha font-light">alpha</span></a> |
||||
|
||||
</div> |
||||
<div class="navbar-collapse collapse"> |
||||
<!-- <ul class="nav navbar-nav"></ul> --> |
||||
<ul class="nav navbar-nav navbar-right"> |
||||
<li><a href="https://github.com/zhukov/webogram" target="_blank">About</a></li> |
||||
<li ng-if="isLoggedIn"><a href="" ng-click="logOut()">Log out</a></li> |
||||
</ul> |
||||
</div><!--/.nav-collapse --> |
||||
</div> |
||||
</div> |
||||
</div> |
@ -0,0 +1,118 @@
|
||||
<div ng-include="'partials/head.html'"></div> |
||||
|
||||
<div class="im_page_wrap"> |
||||
|
||||
<div class="im_page_split clearfix"> |
||||
|
||||
<div class="im_dialogs_col_wrap" ng-controller="AppImDialogsController"> |
||||
<div class="im_dialogs_search"> |
||||
<input class="form-control im_dialogs_search_field" type="search" placeholder="Search" ng-model="searchQuery"/> |
||||
</div> |
||||
<div my-dialogs-list class="im_dialogs_col"> |
||||
<div class="im_dialogs_wrap nano"> |
||||
<div class="im_dialogs_scrollable_wrap content"> |
||||
<ul class="nav nav-pills nav-stacked"> |
||||
<li my-dialog dialog-message="dialogMessage" ng-repeat="dialogMessage in dialogs | filter:searchQuery" ng-class="{active: curDialog.peerID == dialogMessage.peerID}"></li> |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="im_history_col_wrap" ng-controller="AppImHistoryController"> |
||||
<div ng-if="state.notSelected" class="im_history_not_selected">No history to display</div> |
||||
|
||||
<div ng-if="state.loaded"> |
||||
<div my-history class="im_history_col"> |
||||
|
||||
<div class="im_history_panel_wrap"> |
||||
|
||||
<div class="im_history_panel clearfix" ng-controller="AppImPanelController"> |
||||
<div class="im_history_panel_title"> |
||||
|
||||
<div ng-if="historyPeer.id < 0"> |
||||
<h4> |
||||
<!-- <a class="im_history_panel_info_link pull-right" ng-click="openChat(-historyPeer.id)">Info</a> --> |
||||
<span ng-bind-html="historyPeer.data.rTitle"></span> |
||||
<small class="im_chat_users"> |
||||
<ng-pluralize count="historyPeer.data.participants_count" |
||||
when="{'0': 'No members', 'one': '1 member', 'other': '{} members'}"> |
||||
</ng-pluralize> |
||||
</small> |
||||
</h4> |
||||
</div> |
||||
|
||||
<div ng-if="historyPeer.id > 0"> |
||||
<h4> |
||||
<!-- <a class="im_history_panel_info_link pull-right" ng-click="openUser(historyPeer.id)">Info</a> --> |
||||
<span ng-bind-html="historyPeer.data.rFullName"></span> |
||||
<small class="im_peer_online">{{historyPeer.data | userStatus}}</small> |
||||
</h4> |
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
<div class="im_history_wrap nano"> |
||||
|
||||
<div class="im_history_scrollable_wrap content"> |
||||
|
||||
<div class="im_history_scrollable"> |
||||
|
||||
<div class="im_history"> |
||||
<div class="im_history_message_wrap" my-message ng-repeat="historyMessage in history"></div> |
||||
</div> |
||||
|
||||
<div class="im_history_typing_wrap"> |
||||
<div class="im_history_typing" ng-animate="{enter:'animate-show', leave:'animate-hide'}" ng-if="typing.user"> |
||||
<strong class="im_history_typing_author" ng-bind-html="typing.user.rFullName"></strong> is typing.. |
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
<div class="im_send_form_wrap clearfix" ng-controller="AppImSendController"> |
||||
<div class="pull-right im_panel_peer_photo" ng-click="openUser(historyPeer.id)" ng-if="historyPeer.id > 0"> |
||||
<img class="im_panel_peer_photo" my-load-thumb thumb="historyPeer.photo" /> |
||||
<i class="icon im_panel_peer_online" ng-show="historyPeer.data.status._ == 'userStatusOnline'"></i> |
||||
</div> |
||||
<div class="pull-right im_panel_peer_photo" ng-click="openChat(-historyPeer.id)" ng-if="historyPeer.id < 0"> |
||||
<img class="im_panel_peer_photo" my-load-thumb thumb="historyPeer.photo" /> |
||||
</div> |
||||
<div class="pull-left im_panel_own_photo"> |
||||
<img class="im_panel_own_photo" my-load-thumb thumb="ownPhoto" /> |
||||
</div> |
||||
<form my-send-form draft-message="draftMessage" class="im_send_form" ng-submit="sendMessage($event)"> |
||||
<div class="im_send_dropbox_wrap"> Drop photos here to send </div> |
||||
<textarea ng-model="draftMessage.text" placeholder="Write a message..." class="form-control im_message_field"></textarea> |
||||
|
||||
<div class="im_emoji_btn pull-right"> |
||||
<i class="icon icon-emoji"></i> |
||||
</div> |
||||
|
||||
<div class="im_attach pull-right"> |
||||
<input type="file" class="im_attach_input" size="28" multiple="true" /> |
||||
<i class="icon icon-paperclip"></i> |
||||
</div> |
||||
|
||||
<button type="submit" class="btn btn-tg im_submit">Send</button> |
||||
</form> |
||||
</div> |
||||
|
||||
</div> |
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
|
||||
</div> |
||||
|
@ -0,0 +1,28 @@
|
||||
<div ng-include="'partials/head.html'"></div> |
||||
|
||||
|
||||
<div class="login_form_wrap"> |
||||
<div class="error" ng-if="error.message">{{ error.message }}</div> |
||||
<form name="mySendCodeForm" ng-if="!credentials.phone_code_hash" ng-submit="sendCode()"> |
||||
<h3 class="login_form_head">Sign in</h3> |
||||
<p class="login_form_lead">Welcome to an experimental web-client of Telegram messenger</p> |
||||
|
||||
<div class="form-group" ng-class="{'has-error': error.field == 'phone'}"> |
||||
<label class="control-label" for="phone_number" ng-if="error.field == 'phone'">Incorrect phone number</label> |
||||
<input type="text" class="form-control" name="phone_number" ng-model="credentials.phone_number" placeholder="Enter your phone" required> |
||||
</div> |
||||
<button class="btn btn-tg btn-block" type="submit">Next</button> |
||||
</form> |
||||
|
||||
<form name="myLoginForm" ng-if="credentials.phone_code_hash" ng-submit="logIn()"> |
||||
<h3 class="login_form_head">{{ credentials.phone_number | phoneNumber }} <small>(<a href="#/auth">edit</a>)</small></h3> |
||||
<p class="login_form_lead">We have sent you a code via SMS.<br/>Please enter it below.</p> |
||||
|
||||
<div class="form-group" ng-class="{'has-error': error.field == 'phone_code'}"> |
||||
<label class="control-label" for="phone_code" ng-if="error.field == 'phone_code'">Incorrect SMS code</label> |
||||
<input type="text" class="form-control" name="phone_code" ng-model="credentials.phone_code" placeholder="Enter your code" required> |
||||
</div> |
||||
|
||||
<button class="btn btn-tg btn-block" type="submit">Sign in</button> |
||||
</form> |
||||
</div> |
@ -0,0 +1,91 @@
|
||||
<div class="im_message_wrap clearfix" ng-class="{im_message_out: historyMessage.out}"> |
||||
|
||||
<div class="im_service_message_wrap" ng-if="historyMessage._ == 'messageService'"> |
||||
<div class="im_service_message"> |
||||
|
||||
<a ng-click="openUser(historyMessage.from_id)" class="im_message_author" ng-bind-html="historyMessage.fromUser.rFirstName"></a> |
||||
|
||||
<span class="im_message_service" ng-switch="historyMessage.action['_']"> |
||||
<span ng-switch-when="messageActionChatCreate"> |
||||
created the group «<strong>{{historyMessage.action.title}}</strong>» |
||||
</span> |
||||
<span ng-switch-when="messageActionChatEditTitle"> |
||||
changed group name to «<strong>{{historyMessage.action.title}}</strong>» |
||||
</span> |
||||
<span ng-switch-when="messageActionChatEditPhoto"> |
||||
changed group photo |
||||
</span> |
||||
<span ng-switch-when="messageActionChatDeletePhoto"> |
||||
removed group photo |
||||
</span> |
||||
<span ng-switch-when="messageActionChatAddUser"> |
||||
invited <span ng-bind-html="historyMessage.action.user.rFullName"></span> |
||||
</span> |
||||
<span ng-switch-when="messageActionChatDeleteUser"> |
||||
kicked <span ng-bind-html="historyMessage.action.user.rFullName"></span> |
||||
</span> |
||||
|
||||
<span ng-switch-default> |
||||
unsupported action {{historyMessage.action}} |
||||
</span> |
||||
</span> |
||||
|
||||
</div> |
||||
|
||||
<a ng-if="historyMessage.action._ == 'messageActionChatEditPhoto'" class="im_service_message_photo_thumb" href="" ng-click="openPhoto(historyMessage.action.photo.id)"> |
||||
<img class="im_service_message_photo_thumb" my-load-thumb thumb="historyMessage.action.photo.thumb" width="{{historyMessage.action.photo.thumb.width}}" height="{{historyMessage.action.photo.thumb.height}}" /> |
||||
</a> |
||||
|
||||
</div> |
||||
|
||||
<div class="im_content_message_wrap" ng-if="historyMessage._ != 'messageService'"> |
||||
<i ng-if="historyMessage.unread" class="icon icon-message-status-unread"></i> |
||||
<a ng-click="openUser(historyMessage.from_id)" class="im_message_from_photo pull-left"> |
||||
<img class="im_message_from_photo" my-load-thumb thumb="historyMessage.fromPhoto"/> |
||||
</a> |
||||
<div class="im_message_meta pull-right text-right"> |
||||
<i ng-if="historyMessage.out" class="icon icon-message-status-tick" ng-class="{'message-status-delivered-tick': true, 'message-status-unread-tick': historyMessage.unread}"></i> |
||||
<span class="im_message_date">{{historyMessage.date | dateOrTime}}</span> |
||||
</div> |
||||
|
||||
<div class="im_message_body"> |
||||
|
||||
<div class="im_message_author" ng-bind-html="historyMessage.fromUser.rFirstName"></div> |
||||
|
||||
<div class="im_message_media" ng-if="historyMessage.media && historyMessage.media._ != 'messageMediaEmpty'" ng-switch="historyMessage.media._"> |
||||
|
||||
<a ng-switch-when="messageMediaPhoto" class="im_message_photo_thumb" href="" ng-click="openPhoto(historyMessage.media.photo.id)" > |
||||
<img class="im_message_photo_thumb" my-load-thumb thumb="historyMessage.media.photo.thumb" width="{{historyMessage.media.photo.thumb.width}}" height="{{historyMessage.media.photo.thumb.height}}" /> |
||||
</a> |
||||
|
||||
<a ng-switch-when="messageMediaVideo" class="im_message_video_thumb" href="" ng-click="openVideo(historyMessage.media.video.id)"> |
||||
<img class="im_message_video_thumb" my-load-thumb thumb="historyMessage.media.video.thumb" width="{{historyMessage.media.video.thumb.width}}" height="{{historyMessage.media.video.thumb.height}}" /> |
||||
<div class="im_message_video_duration_wrap" style="width: {{historyMessage.media.video.thumb.width}}px;"> |
||||
<span class="im_message_video_duration pull-right">{{historyMessage.media.video.duration | duration}}</span> |
||||
<span class="glyphicon glyphicon-facetime-video"></span> |
||||
</div> |
||||
</a> |
||||
|
||||
<a ng-switch-when="messageMediaDocument" class="im_message_document" href="" ng-click="openDoc(historyMessage.media.document.id)"> |
||||
<i class="icon icon-document"></i> |
||||
<div class="im_message_document_name"><strong>{{historyMessage.media.document.file_name}}</strong> {{historyMessage.media.document.size | formatSize}}</div> |
||||
</a> |
||||
|
||||
<a ng-switch-when="messageMediaGeo" my-map-point point="historyMessage.media.geo" class="im_message_geopoint"> |
||||
<i class="icon icon-geo-point"></i> |
||||
</a> |
||||
|
||||
<div ng-switch-when="messageMediaContact"> |
||||
<a ng-click="openUser(historyMessage.media.user.id)" class="im_message_contact_photo pull-left" ng-if="historyMessage.media.user"> |
||||
<img class="im_message_contact_photo" my-load-thumb thumb="historyMessage.media.userPhoto"/> |
||||
</a> |
||||
<div class="im_message_contact_name"><span class="glyphicon glyphicon-user"></span> {{historyMessage.media.first_name}} {{historyMessage.media.last_name}}</div> |
||||
<div class="im_message_contact_phone">{{historyMessage.media.phone_number}}</div> |
||||
</div> |
||||
</div> |
||||
|
||||
<div class="im_message_text" ng-if="historyMessage.message" ng-bind-html="historyMessage.richMessage"></div> |
||||
</div> |
||||
|
||||
</div> |
||||
</div> |
@ -0,0 +1,11 @@
|
||||
<div class="media_modal_wrap photo_modal_wrap"> |
||||
|
||||
<div class="modal-body"> |
||||
|
||||
<div class="photo_modal_image_wrap" my-load-full-photo full-photo="photo.full" thumb-location="photo.thumb.location" ng-click="$close()"> </div> |
||||
|
||||
<p class="media_modal_info">From: <span class="media_modal_author">{{photo.fromUser | userName}}</span>, {{photo.date | dateOrTime}}</p> |
||||
|
||||
</div> |
||||
|
||||
</div> |
@ -0,0 +1,25 @@
|
||||
<div class="user_modal_wrap"> |
||||
|
||||
<div class="modal-header"> |
||||
<!-- <a class="modal-close-link" ng-click="$close()">Close</a> --> |
||||
<h4 class="modal-title">Info</h4> |
||||
</div> |
||||
|
||||
|
||||
<div class="modal-body"> |
||||
|
||||
<div class="user_modal_image_wrap pull-left"> |
||||
<img class="user_modal_image" my-load-thumb thumb="user.thumb"/> |
||||
</div> |
||||
|
||||
<div class="user_modal_info_wrap clearfix"> |
||||
<h4 class="user_modal_header">{{user | userName}}</h4> |
||||
<p class="user_modal_status" ng-if="user.status">{{user | userStatus}}</p> |
||||
|
||||
<p class="user_modal_phone" ng-if="user.phone">{{user.phone | phoneNumber}}</p> |
||||
<button class="btn btn-link user_modal_send_btn" ng-click="goToHistory()">Send message</button> |
||||
</div> |
||||
|
||||
</div> |
||||
|
||||
</div> |
@ -0,0 +1,11 @@
|
||||
<div class="media_modal_wrap video_modal_wrap"> |
||||
|
||||
<div class="modal-body"> |
||||
|
||||
<div class="video_modal_image_wrap" my-load-video video="video"></div> |
||||
|
||||
<p class="media_modal_info">From: <span class="media_modal_author">{{video.fromUser | userName}}</span>, {{video.date | dateOrTime}}</p> |
||||
|
||||
</div> |
||||
|
||||
</div> |
@ -0,0 +1,5 @@
|
||||
<!-- <div class="jumbotron"> |
||||
<h1>Welcome to Telegram!</h1> |
||||
<p>You are currently not authorized. Would you like to log in?</p> |
||||
<p><a class="btn btn-primary btn-lg" role="button" href="#/login">Log in</a></p> |
||||
</div> --> |
@ -0,0 +1,81 @@
|
||||
## Third party libraries |
||||
|
||||
### [AngularJS](http://angularjs.org/) |
||||
|
||||
**Author**: Google, Inc. |
||||
**License**: MIT, https://github.com/angular/angular.js/blob/master/LICENSE |
||||
|
||||
### [JSBN](http://www-cs-students.stanford.edu/~tjw/jsbn/) |
||||
|
||||
**Author**: Tom Wu |
||||
**License**: BSD, http://www-cs-students.stanford.edu/~tjw/jsbn/LICENSE |
||||
|
||||
Biginteger for RSA, PQ prime factorization |
||||
|
||||
|
||||
### [jQuery Emojiarea](https://github.com/diy/jquery-emojiarea) |
||||
|
||||
**Author**: diy |
||||
**License**: Apache, Version 2.0, https://github.com/diy/jquery-emojiarea#license |
||||
|
||||
Emoji keyboard and rich-textfield for composing messages with emoticons |
||||
|
||||
|
||||
### [UI Bootstrap](http://angular-ui.github.io/bootstrap/) |
||||
|
||||
**Author**: AngularUI Team, https://github.com/organizations/angular-ui/teams/291112 |
||||
**License**: MIT, https://github.com/angular-ui/bootstrap/blob/master/LICENSE |
||||
|
||||
Modal windows |
||||
|
||||
|
||||
### [jQuery](https://github.com/jquery/jquery) |
||||
|
||||
**Author**: jQuery Foundation and other contributors |
||||
**License**: MIT, https://github.com/jquery/jquery/blob/master/MIT-LICENSE.txt |
||||
|
||||
Dom manupulations. |
||||
|
||||
### [Bootstrap](https://github.com/twbs/bootstrap) |
||||
|
||||
**Author**: Twitter, Inc |
||||
**License**: MIT, https://github.com/twbs/bootstrap/blob/master/LICENSE |
||||
|
||||
Normalize, CSS-framework |
||||
|
||||
### [nanoScrollerJS](https://github.com/jamesflorentino/nanoScrollerJS) |
||||
|
||||
**Author**: James Florentino |
||||
**License**: MIT, https://github.com/jamesflorentino/nanoScrollerJS/blob/master/LICENSE-MIT |
||||
|
||||
Beautiful OS X Lion-like scrollbars |
||||
|
||||
|
||||
### [CryptoJS](https://code.google.com/p/crypto-js/) |
||||
|
||||
**Author**: Jeff Mott |
||||
**License**: BSD-3-Clause, https://code.google.com/p/crypto-js/wiki/License |
||||
|
||||
AES, SHA-1 implementation |
||||
|
||||
### [zlib.js](https://github.com/imaya/zlib.js) |
||||
|
||||
**Author**: imaya |
||||
**License**: MIT, https://github.com/imaya/zlib.js/blob/master/LICENSE |
||||
|
||||
GZIP for decompressing server responses |
||||
|
||||
### [emoji-data](https://github.com/iamcal/emoji-data) |
||||
|
||||
**Author**: iamcal |
||||
**License**: not specified |
||||
|
||||
Build emoji list in apropriate format. Generate sheet in future, when needed. |
||||
|
||||
|
||||
### [gemoji](https://github.com/github/gemoji) |
||||
|
||||
**Author**: GitHub Inc, 37signals, id Software, whynne@deviantart, Apple Inc. |
||||
**License**: https://github.com/github/gemoji/blob/master/LICENSE |
||||
|
||||
Emoji images |
@ -0,0 +1,22 @@
|
||||
/* |
||||
AngularJS v1.2.3 |
||||
(c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
License: MIT |
||||
*/ |
||||
(function(C,k,F){'use strict';k.module("ngAnimate",["ng"]).config(["$provide","$animateProvider",function(M,G){var p=k.noop,r=k.forEach,N=G.$$selectors,T=1,h="$$ngAnimateState",H="ng-animate",l={running:!0};M.decorator("$animate",["$delegate","$injector","$sniffer","$rootElement","$timeout","$rootScope","$document",function(v,C,I,g,s,q,F){function O(a){if(a){var d=[],c={};a=a.substr(1).split(".");(I.transitions||I.animations)&&a.push("");for(var e=0;e<a.length;e++){var b=a[e],h=N[b];h&&!c[b]&&(d.push(C.get(h)), |
||||
c[b]=!0)}return d}}function m(a,d,c,e,b,l,q){function w(a){t();if(!0===a)u();else{if(a=c.data(h))a.done=u,c.data(h,a);m(x,"after",u)}}function m(e,b,h){var k=b+"End";r(e,function(l,f){var B=function(){a:{var B=b+"Complete",a=e[f];a[B]=!0;(a[k]||p)();for(a=0;a<e.length;a++)if(!e[a][B])break a;h()}};"before"!=b||"enter"!=a&&"move"!=a?l[b]?l[k]=y?l[b](c,d,B):l[b](c,B):B():B()})}function g(){q&&s(q,0,!1)}function t(){t.hasBeenRun||(t.hasBeenRun=!0,l())}function u(){if(!u.hasBeenRun){u.hasBeenRun=!0;var a= |
||||
c.data(h);a&&(y?z(c):(a.closeAnimationTimeout=s(function(){z(c)},0,!1),c.data(h,a)));g()}}var k=c.attr("class")||"",v=(" "+(k+" "+d)).replace(/\s+/g,".");e||(e=b?b.parent():c.parent());var v=O(v),y="addClass"==a||"removeClass"==a;b=c.data(h)||{};if(J(c,e)||0===v.length)t(),u();else{var x=[];b.running&&y&&b.structural||r(v,function(b){if(!b.allowCancel||b.allowCancel(c,a,d)){var e=b[a];"leave"==a?(b=e,e=null):b=b["before"+a.charAt(0).toUpperCase()+a.substr(1)];x.push({before:b,after:e})}});0===x.length? |
||||
(t(),g()):(e=" "+k+" ",b.running&&(s.cancel(b.closeAnimationTimeout),z(c),L(b.animations),b.beforeComplete?(b.done||p)(!0):y&&!b.structural&&(e="removeClass"==b.event?e.replace(b.className,""):e+b.className+" ")),k=" "+d+" ","addClass"==a&&0<=e.indexOf(k)||"removeClass"==a&&-1==e.indexOf(k)?(t(),g()):(c.addClass(H),c.data(h,{running:!0,event:a,className:d,structural:!y,animations:x,done:w}),m(x,"before",w)))}}function E(a){a=a[0];a.nodeType==T&&r(a.querySelectorAll("."+H),function(a){a=k.element(a); |
||||
var c=a.data(h);c&&(L(c.animations),z(a))})}function L(a){r(a,function(d){a.beforeComplete||(d.beforeEnd||p)(!0);a.afterComplete||(d.afterEnd||p)(!0)})}function z(a){a[0]==g[0]?l.disabled||(l.running=!1,l.structural=!1):(a.removeClass(H),a.removeData(h))}function J(a,d){if(l.disabled)return!0;if(a[0]==g[0])return l.disabled||l.running;do{if(0===d.length)break;var c=d[0]==g[0],e=c?l:d.data(h),e=e&&(!!e.disabled||!!e.running);if(c||e)return e;if(c)break}while(d=d.parent());return!0}g.data(h,l);q.$$postDigest(function(){q.$$postDigest(function(){l.running= |
||||
!1})});return{enter:function(a,d,c,e){this.enabled(!1,a);v.enter(a,d,c);q.$$postDigest(function(){m("enter","ng-enter",a,d,c,p,e)})},leave:function(a,d){E(a);this.enabled(!1,a);q.$$postDigest(function(){m("leave","ng-leave",a,null,null,function(){v.leave(a)},d)})},move:function(a,d,c,e){E(a);this.enabled(!1,a);v.move(a,d,c);q.$$postDigest(function(){m("move","ng-move",a,d,c,p,e)})},addClass:function(a,d,c){m("addClass",d,a,null,null,function(){v.addClass(a,d)},c)},removeClass:function(a,d,c){m("removeClass", |
||||
d,a,null,null,function(){v.removeClass(a,d)},c)},enabled:function(a,d){switch(arguments.length){case 2:if(a)z(d);else{var c=d.data(h)||{};c.disabled=!0;d.data(h,c)}break;case 1:l.disabled=!a;break;default:a=!l.disabled}return!!a}}}]);G.register("",["$window","$sniffer","$timeout",function(l,h,I){function g(f){R.push(f);I.cancel(S);S=I(function(){r(R,function(f){f()});R=[];S=null;D={}},10,!1)}function s(f,a){var K=a?D[a]:null;if(!K){var b=0,c=0,d=0,e=0,h,k,g,m;r(f,function(f){if(f.nodeType==T){f=l.getComputedStyle(f)|| |
||||
{};g=f[A+G];b=Math.max(q(g),b);m=f[A+t];h=f[A+u];c=Math.max(q(h),c);k=f[w+u];e=Math.max(q(k),e);var a=q(f[w+G]);0<a&&(a*=parseInt(f[w+M],10)||1);d=Math.max(a,d)}});K={total:0,transitionPropertyStyle:m,transitionDurationStyle:g,transitionDelayStyle:h,transitionDelay:c,transitionDuration:b,animationDelayStyle:k,animationDelay:e,animationDuration:d};a&&(D[a]=K)}return K}function q(f){var a=0;f=k.isString(f)?f.split(/\s*,\s*/):[];r(f,function(f){a=Math.max(parseFloat(f)||0,a)});return a}function H(f){var a= |
||||
f.parent(),b=a.data(V);b||(a.data(V,++U),b=U);return b+"-"+f[0].className}function O(f,a){var b=H(f),c=b+" "+a,d={},e=D[c]?++D[c].total:0;if(0<e){var h=a+"-stagger",d=b+" "+h;(b=!D[d])&&f.addClass(h);d=s(f,d);b&&f.removeClass(h)}f.addClass(a);c=s(f,c);h=Math.max(c.transitionDuration,c.animationDuration);if(0===h)return f.removeClass(a),!1;var k="";0<c.transitionDuration?(f.addClass(x),k+=N+" ",f[0].style[A+t]="none"):f[0].style[w]="none 0s";r(a.split(" "),function(a,f){k+=(0<f?" ":"")+a+"-active"}); |
||||
f.data(y,{className:a,activeClassName:k,maxDuration:h,classes:a+" "+k,timings:c,stagger:d,ii:e});return!0}function m(a){a=a[0];var b=A+t;a.style[b]&&0<a.style[b].length&&(a.style[b]="")}function E(a){var b=a[0],c=w;b.style[c]&&0<b.style[c].length&&(a[0].style[c]="")}function L(a,d,e){function k(a){a.stopPropagation();a=a.originalEvent||a;var f=a.$manualTimeStamp||a.timeStamp||Date.now();Math.max(f-w,0)>=v&&a.elapsedTime>=q&&e()}var n=a.data(y);if(a.hasClass(d)&&n){var l=a[0],g=n.timings,m=n.stagger, |
||||
q=n.maxDuration,r=n.activeClassName,v=1E3*Math.max(g.transitionDelay,g.animationDelay),w=Date.now(),t=Q+" "+P,u=n.ii,x,n="",p=[];if(0<g.transitionDuration){var s=g.transitionPropertyStyle;-1==s.indexOf("all")&&(x=!0,n+=b+"transition-property: "+s+", "+(h.msie?"-ms-zoom":"border-spacing")+"; ",n+=b+"transition-duration: "+g.transitionDurationStyle+", "+g.transitionDuration+"s; ",p.push(b+"transition-property"),p.push(b+"transition-duration"))}0<u&&(0<m.transitionDelay&&0===m.transitionDuration&&(s= |
||||
g.transitionDelayStyle,x&&(s+=", "+g.transitionDelay+"s"),n+=b+"transition-delay: "+z(s,m.transitionDelay,u)+"; ",p.push(b+"transition-delay")),0<m.animationDelay&&0===m.animationDuration&&(n+=b+"animation-delay: "+z(g.animationDelayStyle,m.animationDelay,u)+"; ",p.push(b+"animation-delay")));0<p.length&&(g=l.getAttribute("style")||"",l.setAttribute("style",g+" "+n));a.on(t,k);a.addClass(r);return function(b){a.off(t,k);a.removeClass(r);c(a,d);for(var e in p)l.style.removeProperty(p[e])}}e()}function z(a, |
||||
b,c){var d="";r(a.split(","),function(a,f){d+=(0<f?",":"")+(c*b+parseInt(a,10))+"s"});return d}function J(a,b){if(O(a,b))return function(d){d&&c(a,b)}}function a(a,b,d){if(a.data(y))return L(a,b,d);c(a,b);d()}function d(f,b,c){var d=J(f,b);if(d){var e=d;g(function(){m(f);E(f);e=a(f,b,c)});return function(a){(e||p)(a)}}c()}function c(a,b){a.removeClass(b);a.removeClass(x);a.removeData(y)}function e(a,b){var c="";a=k.isArray(a)?a:a.split(/\s+/);r(a,function(a,f){a&&0<a.length&&(c+=(0<f?" ":"")+a+b)}); |
||||
return c}var b="",A,P,w,Q;C.ontransitionend===F&&C.onwebkittransitionend!==F?(b="-webkit-",A="WebkitTransition",P="webkitTransitionEnd transitionend"):(A="transition",P="transitionend");C.onanimationend===F&&C.onwebkitanimationend!==F?(b="-webkit-",w="WebkitAnimation",Q="webkitAnimationEnd animationend"):(w="animation",Q="animationend");var G="Duration",t="Property",u="Delay",M="IterationCount",V="$$ngAnimateKey",y="$$ngAnimateCSS3Data",x="ng-animate-start",N="ng-animate-active",D={},U=0,R=[],S;return{allowCancel:function(a, |
||||
b,c){var d=(a.data(y)||{}).classes;if(!d||0<=["enter","leave","move"].indexOf(b))return!0;var h=a.parent(),g=k.element(a[0].cloneNode());g.attr("style","position:absolute; top:-9999px; left:-9999px");g.removeAttr("id");g.html("");r(d.split(" "),function(a){g.removeClass(a)});g.addClass(e(c,"addClass"==b?"-add":"-remove"));h.append(g);a=s(g);g.remove();return 0<Math.max(a.transitionDuration,a.animationDuration)},enter:function(a,b){return d(a,"ng-enter",b)},leave:function(a,b){return d(a,"ng-leave", |
||||
b)},move:function(a,b){return d(a,"ng-move",b)},beforeAddClass:function(a,b,c){if(b=J(a,e(b,"-add")))return g(function(){m(a);E(a);c()}),b;c()},addClass:function(b,c,d){return a(b,e(c,"-add"),d)},beforeRemoveClass:function(a,b,c){if(b=J(a,e(b,"-remove")))return g(function(){m(a);E(a);c()}),b;c()},removeClass:function(b,c,d){return a(b,e(c,"-remove"),d)}}}])}])})(window,window.angular); |
||||
//# sourceMappingURL=angular-animate.min.js.map
|
@ -0,0 +1,202 @@
|
||||
/** |
||||
* @license AngularJS v1.2.3 |
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* License: MIT |
||||
*/ |
||||
(function(window, angular, undefined) {'use strict'; |
||||
|
||||
/** |
||||
* @ngdoc overview |
||||
* @name ngCookies |
||||
* @description |
||||
* |
||||
* # ngCookies |
||||
* |
||||
* The `ngCookies` module provides a convenient wrapper for reading and writing browser cookies.
|
||||
* |
||||
* {@installModule cookies} |
||||
* |
||||
* <div doc-module-components="ngCookies"></div> |
||||
* |
||||
* See {@link ngCookies.$cookies `$cookies`} and |
||||
* {@link ngCookies.$cookieStore `$cookieStore`} for usage. |
||||
*/ |
||||
|
||||
|
||||
angular.module('ngCookies', ['ng']). |
||||
/** |
||||
* @ngdoc object |
||||
* @name ngCookies.$cookies |
||||
* @requires $browser |
||||
* |
||||
* @description |
||||
* Provides read/write access to browser's cookies. |
||||
* |
||||
* Only a simple Object is exposed and by adding or removing properties to/from |
||||
* this object, new cookies are created/deleted at the end of current $eval. |
||||
* |
||||
* Requires the {@link ngCookies `ngCookies`} module to be installed. |
||||
* |
||||
* @example |
||||
<doc:example> |
||||
<doc:source> |
||||
<script> |
||||
function ExampleController($cookies) { |
||||
// Retrieving a cookie
|
||||
var favoriteCookie = $cookies.myFavorite; |
||||
// Setting a cookie
|
||||
$cookies.myFavorite = 'oatmeal'; |
||||
} |
||||
</script> |
||||
</doc:source> |
||||
</doc:example> |
||||
*/ |
||||
factory('$cookies', ['$rootScope', '$browser', function ($rootScope, $browser) { |
||||
var cookies = {}, |
||||
lastCookies = {}, |
||||
lastBrowserCookies, |
||||
runEval = false, |
||||
copy = angular.copy, |
||||
isUndefined = angular.isUndefined; |
||||
|
||||
//creates a poller fn that copies all cookies from the $browser to service & inits the service
|
||||
$browser.addPollFn(function() { |
||||
var currentCookies = $browser.cookies(); |
||||
if (lastBrowserCookies != currentCookies) { //relies on browser.cookies() impl
|
||||
lastBrowserCookies = currentCookies; |
||||
copy(currentCookies, lastCookies); |
||||
copy(currentCookies, cookies); |
||||
if (runEval) $rootScope.$apply(); |
||||
} |
||||
})(); |
||||
|
||||
runEval = true; |
||||
|
||||
//at the end of each eval, push cookies
|
||||
//TODO: this should happen before the "delayed" watches fire, because if some cookies are not
|
||||
// strings or browser refuses to store some cookies, we update the model in the push fn.
|
||||
$rootScope.$watch(push); |
||||
|
||||
return cookies; |
||||
|
||||
|
||||
/** |
||||
* Pushes all the cookies from the service to the browser and verifies if all cookies were |
||||
* stored. |
||||
*/ |
||||
function push() { |
||||
var name, |
||||
value, |
||||
browserCookies, |
||||
updated; |
||||
|
||||
//delete any cookies deleted in $cookies
|
||||
for (name in lastCookies) { |
||||
if (isUndefined(cookies[name])) { |
||||
$browser.cookies(name, undefined); |
||||
} |
||||
} |
||||
|
||||
//update all cookies updated in $cookies
|
||||
for(name in cookies) { |
||||
value = cookies[name]; |
||||
if (!angular.isString(value)) { |
||||
if (angular.isDefined(lastCookies[name])) { |
||||
cookies[name] = lastCookies[name]; |
||||
} else { |
||||
delete cookies[name]; |
||||
} |
||||
} else if (value !== lastCookies[name]) { |
||||
$browser.cookies(name, value); |
||||
updated = true; |
||||
} |
||||
} |
||||
|
||||
//verify what was actually stored
|
||||
if (updated){ |
||||
updated = false; |
||||
browserCookies = $browser.cookies(); |
||||
|
||||
for (name in cookies) { |
||||
if (cookies[name] !== browserCookies[name]) { |
||||
//delete or reset all cookies that the browser dropped from $cookies
|
||||
if (isUndefined(browserCookies[name])) { |
||||
delete cookies[name]; |
||||
} else { |
||||
cookies[name] = browserCookies[name]; |
||||
} |
||||
updated = true; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
}]). |
||||
|
||||
|
||||
/** |
||||
* @ngdoc object |
||||
* @name ngCookies.$cookieStore |
||||
* @requires $cookies |
||||
* |
||||
* @description |
||||
* Provides a key-value (string-object) storage, that is backed by session cookies. |
||||
* Objects put or retrieved from this storage are automatically serialized or |
||||
* deserialized by angular's toJson/fromJson. |
||||
* |
||||
* Requires the {@link ngCookies `ngCookies`} module to be installed. |
||||
* |
||||
* @example |
||||
*/ |
||||
factory('$cookieStore', ['$cookies', function($cookies) { |
||||
|
||||
return { |
||||
/** |
||||
* @ngdoc method |
||||
* @name ngCookies.$cookieStore#get |
||||
* @methodOf ngCookies.$cookieStore |
||||
* |
||||
* @description |
||||
* Returns the value of given cookie key |
||||
* |
||||
* @param {string} key Id to use for lookup. |
||||
* @returns {Object} Deserialized cookie value. |
||||
*/ |
||||
get: function(key) { |
||||
var value = $cookies[key]; |
||||
return value ? angular.fromJson(value) : value; |
||||
}, |
||||
|
||||
/** |
||||
* @ngdoc method |
||||
* @name ngCookies.$cookieStore#put |
||||
* @methodOf ngCookies.$cookieStore |
||||
* |
||||
* @description |
||||
* Sets a value for given cookie key |
||||
* |
||||
* @param {string} key Id for the `value`. |
||||
* @param {Object} value Value to be stored. |
||||
*/ |
||||
put: function(key, value) { |
||||
$cookies[key] = angular.toJson(value); |
||||
}, |
||||
|
||||
/** |
||||
* @ngdoc method |
||||
* @name ngCookies.$cookieStore#remove |
||||
* @methodOf ngCookies.$cookieStore |
||||
* |
||||
* @description |
||||
* Remove given cookie |
||||
* |
||||
* @param {string} key Id of the key-value pair to delete. |
||||
*/ |
||||
remove: function(key) { |
||||
delete $cookies[key]; |
||||
} |
||||
}; |
||||
|
||||
}]); |
||||
|
||||
|
||||
})(window, window.angular); |
@ -0,0 +1,8 @@
|
||||
/* |
||||
AngularJS v1.2.3 |
||||
(c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
License: MIT |
||||
*/ |
||||
(function(p,f,n){'use strict';f.module("ngCookies",["ng"]).factory("$cookies",["$rootScope","$browser",function(d,b){var c={},g={},h,k=!1,l=f.copy,m=f.isUndefined;b.addPollFn(function(){var a=b.cookies();h!=a&&(h=a,l(a,g),l(a,c),k&&d.$apply())})();k=!0;d.$watch(function(){var a,e,d;for(a in g)m(c[a])&&b.cookies(a,n);for(a in c)(e=c[a],f.isString(e))?e!==g[a]&&(b.cookies(a,e),d=!0):f.isDefined(g[a])?c[a]=g[a]:delete c[a];if(d)for(a in e=b.cookies(),c)c[a]!==e[a]&&(m(e[a])?delete c[a]:c[a]=e[a])}); |
||||
return c}]).factory("$cookieStore",["$cookies",function(d){return{get:function(b){return(b=d[b])?f.fromJson(b):b},put:function(b,c){d[b]=f.toJson(c)},remove:function(b){delete d[b]}}}])})(window,window.angular); |
||||
//# sourceMappingURL=angular-cookies.min.js.map
|
@ -0,0 +1,8 @@
|
||||
{ |
||||
"version":3, |
||||
"file":"angular-cookies.min.js", |
||||
"lineCount":7, |
||||
"mappings":"A;;;;;aAKC,SAAQ,CAACA,CAAD,CAASC,CAAT,CAAkBC,CAAlB,CAA6B,CAoBtCD,CAAAE,OAAA,CAAe,WAAf,CAA4B,CAAC,IAAD,CAA5B,CAAAC,QAAA,CA4BW,UA5BX,CA4BuB,CAAC,YAAD,CAAe,UAAf,CAA2B,QAAS,CAACC,CAAD,CAAaC,CAAb,CAAuB,CAAA,IACxEC,EAAU,EAD8D,CAExEC,EAAc,EAF0D,CAGxEC,CAHwE,CAIxEC,EAAU,CAAA,CAJ8D,CAKxEC,EAAOV,CAAAU,KALiE,CAMxEC,EAAcX,CAAAW,YAGlBN,EAAAO,UAAA,CAAmB,QAAQ,EAAG,CAC5B,IAAIC,EAAiBR,CAAAC,QAAA,EACjBE,EAAJ,EAA0BK,CAA1B,GACEL,CAGA,CAHqBK,CAGrB,CAFAH,CAAA,CAAKG,CAAL,CAAqBN,CAArB,CAEA,CADAG,CAAA,CAAKG,CAAL,CAAqBP,CAArB,CACA,CAAIG,CAAJ,EAAaL,CAAAU,OAAA,EAJf,CAF4B,CAA9B,CAAA,EAUAL,EAAA,CAAU,CAAA,CAKVL,EAAAW,OAAA,CASAC,QAAa,EAAG,CAAA,IACVC,CADU,CAEVC,CAFU,CAIVC,CAGJ,KAAKF,CAAL,GAAaV,EAAb,CACMI,CAAA,CAAYL,CAAA,CAAQW,CAAR,CAAZ,CAAJ,EACEZ,CAAAC,QAAA,CAAiBW,CAAjB,CAAuBhB,CAAvB,CAKJ,KAAIgB,CAAJ,GAAYX,EAAZ,CAEE,CADAY,CACK,CADGZ,CAAA,CAAQW,CAAR,CACH,CAAAjB,CAAAoB,SAAA,CAAiBF,CAAjB,CAAL,EAMWA,CANX,GAMqBX,CAAA,CAAYU,CAAZ,CANrB,GAOEZ,CAAAC,QAAA,CAAiBW,CAAjB,CAAuBC,CAAvB,CACA,CAAAC,CAAA,CAAU,CAAA,CARZ,EACMnB,CAAAqB,UAAA,CAAkBd,CAAA,CAAYU,CAAZ,CAAlB,CAAJ,CACEX,CAAA,CAAQW,CAAR,CADF,CACkBV,CAAA,CAAYU,CAAZ,CADlB,CAGE,OAAOX,CAAA,CAAQW,CAAR,CASb,IAAIE,CAAJ,CAIE,IAAKF,CAAL,GAFAK,EAEahB,CAFID,CAAAC,QAAA,EAEJA,CAAAA,CAAb,CACMA,CAAA,CAAQW,CAAR,CAAJ,GAAsBK,CAAA,CAAeL,CAAf,CAAtB,GAEMN,CAAA,CAAYW,CAAA,CAAeL,CAAf,CAAZ,CAAJ,CACE,OAAOX,CAAA,CAAQW,CAAR,CADT,CAGEX,CAAA,CAAQW,CAAR,CAHF,CAGkBK,CAAA,CAAeL,CAAf,CALpB,CAlCU,CAThB,CAEA;MAAOX,EA1BqE,CAA3D,CA5BvB,CAAAH,QAAA,CA4HW,cA5HX,CA4H2B,CAAC,UAAD,CAAa,QAAQ,CAACoB,CAAD,CAAW,CAErD,MAAO,KAYAC,QAAQ,CAACC,CAAD,CAAM,CAEjB,MAAO,CADHP,CACG,CADKK,CAAA,CAASE,CAAT,CACL,EAAQzB,CAAA0B,SAAA,CAAiBR,CAAjB,CAAR,CAAkCA,CAFxB,CAZd,KA4BAS,QAAQ,CAACF,CAAD,CAAMP,CAAN,CAAa,CACxBK,CAAA,CAASE,CAAT,CAAA,CAAgBzB,CAAA4B,OAAA,CAAeV,CAAf,CADQ,CA5BrB,QA0CGW,QAAQ,CAACJ,CAAD,CAAM,CACpB,OAAOF,CAAA,CAASE,CAAT,CADa,CA1CjB,CAF8C,CAAhC,CA5H3B,CApBsC,CAArC,CAAA,CAoME1B,MApMF,CAoMUA,MAAAC,QApMV;", |
||||
"sources":["angular-cookies.js"], |
||||
"names":["window","angular","undefined","module","factory","$rootScope","$browser","cookies","lastCookies","lastBrowserCookies","runEval","copy","isUndefined","addPollFn","currentCookies","$apply","$watch","push","name","value","updated","isString","isDefined","browserCookies","$cookies","get","key","fromJson","put","toJson","remove"] |
||||
} |
@ -0,0 +1,24 @@
|
||||
/* Include this file in your html if you are using the CSP mode. */ |
||||
|
||||
@charset "UTF-8"; |
||||
|
||||
[ng\:cloak], [ng-cloak], [data-ng-cloak], [x-ng-cloak], |
||||
.ng-cloak, .x-ng-cloak, |
||||
.ng-hide { |
||||
display: none !important; |
||||
} |
||||
|
||||
ng\:form { |
||||
display: block; |
||||
} |
||||
|
||||
/* The styles below ensure that the CSS transition will ALWAYS |
||||
* animate and close. A nasty bug occurs with CSS transitions where |
||||
* when the active class isn't set, or if the active class doesn't |
||||
* contain any styles to transition to, then, if ngAnimate is used, |
||||
* it will appear as if the webpage is broken due to the forever hanging |
||||
* animations. The border-spacing (!ie) and zoom (ie) CSS properties are |
||||
* used below since they trigger a transition without making the browser |
||||
* animate anything and they're both highly underused CSS properties */ |
||||
.ng-animate-start { border-spacing:1px 1px; -ms-zoom:1.0001; } |
||||
.ng-animate-active { border-spacing:0px 0px; -ms-zoom:1; } |
@ -0,0 +1,410 @@
|
||||
/** |
||||
* @license AngularJS v1.2.3 |
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* License: MIT |
||||
*/ |
||||
|
||||
(function() {'use strict'; |
||||
|
||||
/** |
||||
* @description |
||||
* |
||||
* This object provides a utility for producing rich Error messages within |
||||
* Angular. It can be called as follows: |
||||
* |
||||
* var exampleMinErr = minErr('example'); |
||||
* throw exampleMinErr('one', 'This {0} is {1}', foo, bar); |
||||
* |
||||
* The above creates an instance of minErr in the example namespace. The |
||||
* resulting error will have a namespaced error code of example.one. The |
||||
* resulting error will replace {0} with the value of foo, and {1} with the |
||||
* value of bar. The object is not restricted in the number of arguments it can |
||||
* take. |
||||
* |
||||
* If fewer arguments are specified than necessary for interpolation, the extra |
||||
* interpolation markers will be preserved in the final string. |
||||
* |
||||
* Since data will be parsed statically during a build step, some restrictions |
||||
* are applied with respect to how minErr instances are created and called. |
||||
* Instances should have names of the form namespaceMinErr for a minErr created |
||||
* using minErr('namespace') . Error codes, namespaces and template strings |
||||
* should all be static strings, not variables or general expressions. |
||||
* |
||||
* @param {string} module The namespace to use for the new minErr instance. |
||||
* @returns {function(string, string, ...): Error} instance |
||||
*/ |
||||
|
||||
function minErr(module) { |
||||
return function () { |
||||
var code = arguments[0], |
||||
prefix = '[' + (module ? module + ':' : '') + code + '] ', |
||||
template = arguments[1], |
||||
templateArgs = arguments, |
||||
stringify = function (obj) { |
||||
if (typeof obj === 'function') { |
||||
return obj.toString().replace(/ \{[\s\S]*$/, ''); |
||||
} else if (typeof obj === 'undefined') { |
||||
return 'undefined'; |
||||
} else if (typeof obj !== 'string') { |
||||
return JSON.stringify(obj); |
||||
} |
||||
return obj; |
||||
}, |
||||
message, i; |
||||
|
||||
message = prefix + template.replace(/\{\d+\}/g, function (match) { |
||||
var index = +match.slice(1, -1), arg; |
||||
|
||||
if (index + 2 < templateArgs.length) { |
||||
arg = templateArgs[index + 2]; |
||||
if (typeof arg === 'function') { |
||||
return arg.toString().replace(/ ?\{[\s\S]*$/, ''); |
||||
} else if (typeof arg === 'undefined') { |
||||
return 'undefined'; |
||||
} else if (typeof arg !== 'string') { |
||||
return toJson(arg); |
||||
} |
||||
return arg; |
||||
} |
||||
return match; |
||||
}); |
||||
|
||||
message = message + '\nhttp://errors.angularjs.org/1.2.3/' + |
||||
(module ? module + '/' : '') + code; |
||||
for (i = 2; i < arguments.length; i++) { |
||||
message = message + (i == 2 ? '?' : '&') + 'p' + (i-2) + '=' + |
||||
encodeURIComponent(stringify(arguments[i])); |
||||
} |
||||
|
||||
return new Error(message); |
||||
}; |
||||
} |
||||
|
||||
/** |
||||
* @ngdoc interface |
||||
* @name angular.Module |
||||
* @description |
||||
* |
||||
* Interface for configuring angular {@link angular.module modules}. |
||||
*/ |
||||
|
||||
function setupModuleLoader(window) { |
||||
|
||||
var $injectorMinErr = minErr('$injector'); |
||||
var ngMinErr = minErr('ng'); |
||||
|
||||
function ensure(obj, name, factory) { |
||||
return obj[name] || (obj[name] = factory()); |
||||
} |
||||
|
||||
var angular = ensure(window, 'angular', Object); |
||||
|
||||
// We need to expose `angular.$$minErr` to modules such as `ngResource` that reference it during bootstrap
|
||||
angular.$$minErr = angular.$$minErr || minErr; |
||||
|
||||
return ensure(angular, 'module', function() { |
||||
/** @type {Object.<string, angular.Module>} */ |
||||
var modules = {}; |
||||
|
||||
/** |
||||
* @ngdoc function |
||||
* @name angular.module |
||||
* @description |
||||
* |
||||
* The `angular.module` is a global place for creating, registering and retrieving Angular |
||||
* modules. |
||||
* All modules (angular core or 3rd party) that should be available to an application must be |
||||
* registered using this mechanism. |
||||
* |
||||
* When passed two or more arguments, a new module is created. If passed only one argument, an |
||||
* existing module (the name passed as the first argument to `module`) is retrieved. |
||||
* |
||||
* |
||||
* # Module |
||||
* |
||||
* A module is a collection of services, directives, filters, and configuration information. |
||||
* `angular.module` is used to configure the {@link AUTO.$injector $injector}. |
||||
* |
||||
* <pre> |
||||
* // Create a new module
|
||||
* var myModule = angular.module('myModule', []); |
||||
* |
||||
* // register a new service
|
||||
* myModule.value('appName', 'MyCoolApp'); |
||||
* |
||||
* // configure existing services inside initialization blocks.
|
||||
* myModule.config(function($locationProvider) { |
||||
* // Configure existing providers
|
||||
* $locationProvider.hashPrefix('!'); |
||||
* }); |
||||
* </pre> |
||||
* |
||||
* Then you can create an injector and load your modules like this: |
||||
* |
||||
* <pre> |
||||
* var injector = angular.injector(['ng', 'MyModule']) |
||||
* </pre> |
||||
* |
||||
* However it's more likely that you'll just use |
||||
* {@link ng.directive:ngApp ngApp} or |
||||
* {@link angular.bootstrap} to simplify this process for you. |
||||
* |
||||
* @param {!string} name The name of the module to create or retrieve. |
||||
* @param {Array.<string>=} requires If specified then new module is being created. If |
||||
* unspecified then the the module is being retrieved for further configuration. |
||||
* @param {Function} configFn Optional configuration function for the module. Same as |
||||
* {@link angular.Module#methods_config Module#config()}. |
||||
* @returns {module} new module with the {@link angular.Module} api. |
||||
*/ |
||||
return function module(name, requires, configFn) { |
||||
var assertNotHasOwnProperty = function(name, context) { |
||||
if (name === 'hasOwnProperty') { |
||||
throw ngMinErr('badname', 'hasOwnProperty is not a valid {0} name', context); |
||||
} |
||||
}; |
||||
|
||||
assertNotHasOwnProperty(name, 'module'); |
||||
if (requires && modules.hasOwnProperty(name)) { |
||||
modules[name] = null; |
||||
} |
||||
return ensure(modules, name, function() { |
||||
if (!requires) { |
||||
throw $injectorMinErr('nomod', "Module '{0}' is not available! You either misspelled " + |
||||
"the module name or forgot to load it. If registering a module ensure that you " + |
||||
"specify the dependencies as the second argument.", name); |
||||
} |
||||
|
||||
/** @type {!Array.<Array.<*>>} */ |
||||
var invokeQueue = []; |
||||
|
||||
/** @type {!Array.<Function>} */ |
||||
var runBlocks = []; |
||||
|
||||
var config = invokeLater('$injector', 'invoke'); |
||||
|
||||
/** @type {angular.Module} */ |
||||
var moduleInstance = { |
||||
// Private state
|
||||
_invokeQueue: invokeQueue, |
||||
_runBlocks: runBlocks, |
||||
|
||||
/** |
||||
* @ngdoc property |
||||
* @name angular.Module#requires |
||||
* @propertyOf angular.Module |
||||
* @returns {Array.<string>} List of module names which must be loaded before this module. |
||||
* @description |
||||
* Holds the list of modules which the injector will load before the current module is |
||||
* loaded. |
||||
*/ |
||||
requires: requires, |
||||
|
||||
/** |
||||
* @ngdoc property |
||||
* @name angular.Module#name |
||||
* @propertyOf angular.Module |
||||
* @returns {string} Name of the module. |
||||
* @description |
||||
*/ |
||||
name: name, |
||||
|
||||
|
||||
/** |
||||
* @ngdoc method |
||||
* @name angular.Module#provider |
||||
* @methodOf angular.Module |
||||
* @param {string} name service name |
||||
* @param {Function} providerType Construction function for creating new instance of the |
||||
* service. |
||||
* @description |
||||
* See {@link AUTO.$provide#provider $provide.provider()}. |
||||
*/ |
||||
provider: invokeLater('$provide', 'provider'), |
||||
|
||||
/** |
||||
* @ngdoc method |
||||
* @name angular.Module#factory |
||||
* @methodOf angular.Module |
||||
* @param {string} name service name |
||||
* @param {Function} providerFunction Function for creating new instance of the service. |
||||
* @description |
||||
* See {@link AUTO.$provide#factory $provide.factory()}. |
||||
*/ |
||||
factory: invokeLater('$provide', 'factory'), |
||||
|
||||
/** |
||||
* @ngdoc method |
||||
* @name angular.Module#service |
||||
* @methodOf angular.Module |
||||
* @param {string} name service name |
||||
* @param {Function} constructor A constructor function that will be instantiated. |
||||
* @description |
||||
* See {@link AUTO.$provide#service $provide.service()}. |
||||
*/ |
||||
service: invokeLater('$provide', 'service'), |
||||
|
||||
/** |
||||
* @ngdoc method |
||||
* @name angular.Module#value |
||||
* @methodOf angular.Module |
||||
* @param {string} name service name |
||||
* @param {*} object Service instance object. |
||||
* @description |
||||
* See {@link AUTO.$provide#value $provide.value()}. |
||||
*/ |
||||
value: invokeLater('$provide', 'value'), |
||||
|
||||
/** |
||||
* @ngdoc method |
||||
* @name angular.Module#constant |
||||
* @methodOf angular.Module |
||||
* @param {string} name constant name |
||||
* @param {*} object Constant value. |
||||
* @description |
||||
* Because the constant are fixed, they get applied before other provide methods. |
||||
* See {@link AUTO.$provide#constant $provide.constant()}. |
||||
*/ |
||||
constant: invokeLater('$provide', 'constant', 'unshift'), |
||||
|
||||
/** |
||||
* @ngdoc method |
||||
* @name angular.Module#animation |
||||
* @methodOf angular.Module |
||||
* @param {string} name animation name |
||||
* @param {Function} animationFactory Factory function for creating new instance of an |
||||
* animation. |
||||
* @description |
||||
* |
||||
* **NOTE**: animations take effect only if the **ngAnimate** module is loaded. |
||||
* |
||||
* |
||||
* Defines an animation hook that can be later used with |
||||
* {@link ngAnimate.$animate $animate} service and directives that use this service. |
||||
* |
||||
* <pre> |
||||
* module.animation('.animation-name', function($inject1, $inject2) { |
||||
* return { |
||||
* eventName : function(element, done) { |
||||
* //code to run the animation
|
||||
* //once complete, then run done()
|
||||
* return function cancellationFunction(element) { |
||||
* //code to cancel the animation
|
||||
* } |
||||
* } |
||||
* } |
||||
* }) |
||||
* </pre> |
||||
* |
||||
* See {@link ngAnimate.$animateProvider#register $animateProvider.register()} and |
||||
* {@link ngAnimate ngAnimate module} for more information. |
||||
*/ |
||||
animation: invokeLater('$animateProvider', 'register'), |
||||
|
||||
/** |
||||
* @ngdoc method |
||||
* @name angular.Module#filter |
||||
* @methodOf angular.Module |
||||
* @param {string} name Filter name. |
||||
* @param {Function} filterFactory Factory function for creating new instance of filter. |
||||
* @description |
||||
* See {@link ng.$filterProvider#register $filterProvider.register()}. |
||||
*/ |
||||
filter: invokeLater('$filterProvider', 'register'), |
||||
|
||||
/** |
||||
* @ngdoc method |
||||
* @name angular.Module#controller |
||||
* @methodOf angular.Module |
||||
* @param {string|Object} name Controller name, or an object map of controllers where the |
||||
* keys are the names and the values are the constructors. |
||||
* @param {Function} constructor Controller constructor function. |
||||
* @description |
||||
* See {@link ng.$controllerProvider#register $controllerProvider.register()}. |
||||
*/ |
||||
controller: invokeLater('$controllerProvider', 'register'), |
||||
|
||||
/** |
||||
* @ngdoc method |
||||
* @name angular.Module#directive |
||||
* @methodOf angular.Module |
||||
* @param {string|Object} name Directive name, or an object map of directives where the |
||||
* keys are the names and the values are the factories. |
||||
* @param {Function} directiveFactory Factory function for creating new instance of |
||||
* directives. |
||||
* @description |
||||
* See {@link ng.$compileProvider#methods_directive $compileProvider.directive()}. |
||||
*/ |
||||
directive: invokeLater('$compileProvider', 'directive'), |
||||
|
||||
/** |
||||
* @ngdoc method |
||||
* @name angular.Module#config |
||||
* @methodOf angular.Module |
||||
* @param {Function} configFn Execute this function on module load. Useful for service |
||||
* configuration. |
||||
* @description |
||||
* Use this method to register work which needs to be performed on module loading. |
||||
*/ |
||||
config: config, |
||||
|
||||
/** |
||||
* @ngdoc method |
||||
* @name angular.Module#run |
||||
* @methodOf angular.Module |
||||
* @param {Function} initializationFn Execute this function after injector creation. |
||||
* Useful for application initialization. |
||||
* @description |
||||
* Use this method to register work which should be performed when the injector is done |
||||
* loading all modules. |
||||
*/ |
||||
run: function(block) { |
||||
runBlocks.push(block); |
||||
return this; |
||||
} |
||||
}; |
||||
|
||||
if (configFn) { |
||||
config(configFn); |
||||
} |
||||
|
||||
return moduleInstance; |
||||
|
||||
/** |
||||
* @param {string} provider |
||||
* @param {string} method |
||||
* @param {String=} insertMethod |
||||
* @returns {angular.Module} |
||||
*/ |
||||
function invokeLater(provider, method, insertMethod) { |
||||
return function() { |
||||
invokeQueue[insertMethod || 'push']([provider, method, arguments]); |
||||
return moduleInstance; |
||||
}; |
||||
} |
||||
}); |
||||
}; |
||||
}); |
||||
|
||||
} |
||||
|
||||
setupModuleLoader(window); |
||||
})(window); |
||||
|
||||
/** |
||||
* Closure compiler type information |
||||
* |
||||
* @typedef { { |
||||
* requires: !Array.<string>, |
||||
* invokeQueue: !Array.<Array.<*>>, |
||||
* |
||||
* service: function(string, Function):angular.Module, |
||||
* factory: function(string, Function):angular.Module, |
||||
* value: function(string, *):angular.Module, |
||||
* |
||||
* filter: function(string, Function):angular.Module, |
||||
* |
||||
* init: function(Function):angular.Module |
||||
* } } |
||||
*/ |
||||
angular.Module; |
||||
|
@ -0,0 +1,9 @@
|
||||
/* |
||||
AngularJS v1.2.3 |
||||
(c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
License: MIT |
||||
*/ |
||||
(function(){'use strict';function d(a){return function(){var c=arguments[0],b,c="["+(a?a+":":"")+c+"] http://errors.angularjs.org/1.2.3/"+(a?a+"/":"")+c;for(b=1;b<arguments.length;b++)c=c+(1==b?"?":"&")+"p"+(b-1)+"="+encodeURIComponent("function"==typeof arguments[b]?arguments[b].toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof arguments[b]?"undefined":"string"!=typeof arguments[b]?JSON.stringify(arguments[b]):arguments[b]);return Error(c)}}(function(a){var c=d("$injector"),b=d("ng");a=a.angular|| |
||||
(a.angular={});a.$$minErr=a.$$minErr||d;return a.module||(a.module=function(){var a={};return function(e,d,f){if("hasOwnProperty"===e)throw b("badname","module");d&&a.hasOwnProperty(e)&&(a[e]=null);return a[e]||(a[e]=function(){function a(c,d,e){return function(){b[e||"push"]([c,d,arguments]);return g}}if(!d)throw c("nomod",e);var b=[],h=[],k=a("$injector","invoke"),g={_invokeQueue:b,_runBlocks:h,requires:d,name:e,provider:a("$provide","provider"),factory:a("$provide","factory"),service:a("$provide", |
||||
"service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),animation:a("$animateProvider","register"),filter:a("$filterProvider","register"),controller:a("$controllerProvider","register"),directive:a("$compileProvider","directive"),config:k,run:function(a){h.push(a);return this}};f&&k(f);return g}())}}())})(window)})(window); |
||||
//# sourceMappingURL=angular-loader.min.js.map
|
@ -0,0 +1,8 @@
|
||||
{ |
||||
"version":3, |
||||
"file":"angular-loader.min.js", |
||||
"lineCount":8, |
||||
"mappings":"A;;;;;aAMC,SAAQ,EAAG,CCNZA,QAAS,EAAM,CAAC,CAAD,CAAS,CAWtB,MAAO,SAAS,EAAG,CAAA,IACb,EAAO,SAAA,CAAU,CAAV,CADM,CAIf,CAJe,CAKjB,EAHW,GAGX,EAHkB,CAAA,CAAS,CAAT,CAAkB,GAAlB,CAAwB,EAG1C,EAHgD,CAGhD,CAAmB,sCAAnB,EAA2D,CAAA,CAAS,CAAT,CAAkB,GAAlB,CAAwB,EAAnF,EAAyF,CACzF,KAAK,CAAL,CAAS,CAAT,CAAY,CAAZ,CAAgB,SAAA,OAAhB,CAAkC,CAAA,EAAlC,CACE,CAAA,CAAU,CAAV,EAA0B,CAAL,EAAA,CAAA,CAAS,GAAT,CAAe,GAApC,EAA2C,GAA3C,EAAkD,CAAlD,CAAoD,CAApD,EAAyD,GAAzD,CACE,kBAAA,CAjBc,UAAlB,EAAI,MAiB6B,UAAA,CAAU,CAAV,CAjBjC,CAiBiC,SAAA,CAAU,CAAV,CAhBxB,SAAA,EAAA,QAAA,CAAuB,aAAvB,CAAsC,EAAtC,CADT,CAEyB,WAAlB,EAAI,MAesB,UAAA,CAAU,CAAV,CAf1B,CACE,WADF,CAEoB,QAApB,EAAM,MAaoB,UAAA,CAAU,CAAV,CAb1B,CACE,IAAA,UAAA,CAYwB,SAAA,CAAU,CAAV,CAZxB,CADF,CAa0B,SAAA,CAAU,CAAV,CAA7B,CAEJ,OAAW,MAAJ,CAAU,CAAV,CAVU,CAXG,CD0FxBC,SAA0B,CAACC,CAAD,CAAS,CAEjC,IAAIC,EAAkBH,CAAA,CAAO,WAAP,CAAtB,CACII,EAAWJ,CAAA,CAAO,IAAP,CAMXK,EAAAA,CAAiBH,CAHZ,QAGLG;CAAiBH,CAHE,QAGnBG,CAH+B,EAG/BA,CAGJA,EAAAC,SAAA,CAAmBD,CAAAC,SAAnB,EAAuCN,CAEvC,OAAcK,EARL,OAQT,GAAcA,CARS,OAQvB,CAAiCE,QAAQ,EAAG,CAE1C,IAAIC,EAAU,EAoDd,OAAOC,SAAe,CAACC,CAAD,CAAOC,CAAP,CAAiBC,CAAjB,CAA2B,CAE7C,GAAa,gBAAb,GAKsBF,CALtB,CACE,KAAMN,EAAA,CAAS,SAAT,CAIoBS,QAJpB,CAAN,CAKAF,CAAJ,EAAgBH,CAAAM,eAAA,CAAuBJ,CAAvB,CAAhB,GACEF,CAAA,CAAQE,CAAR,CADF,CACkB,IADlB,CAGA,OAAcF,EAzET,CAyEkBE,CAzElB,CAyEL,GAAcF,CAzEK,CAyEIE,CAzEJ,CAyEnB,CAA6BH,QAAQ,EAAG,CAgNtCQ,QAASA,EAAW,CAACC,CAAD,CAAWC,CAAX,CAAmBC,CAAnB,CAAiC,CACnD,MAAO,SAAQ,EAAG,CAChBC,CAAA,CAAYD,CAAZ,EAA4B,MAA5B,CAAA,CAAoC,CAACF,CAAD,CAAWC,CAAX,CAAmBG,SAAnB,CAApC,CACA,OAAOC,EAFS,CADiC,CA/MrD,GAAI,CAACV,CAAL,CACE,KAAMR,EAAA,CAAgB,OAAhB,CAEiDO,CAFjD,CAAN,CAMF,IAAIS,EAAc,EAAlB,CAGIG,EAAY,EAHhB,CAKIC,EAASR,CAAA,CAAY,WAAZ,CAAyB,QAAzB,CALb,CAQIM,EAAiB,cAELF,CAFK,YAGPG,CAHO,UAcTX,CAdS,MAuBbD,CAvBa,UAoCTK,CAAA,CAAY,UAAZ,CAAwB,UAAxB,CApCS,SA+CVA,CAAA,CAAY,UAAZ,CAAwB,SAAxB,CA/CU,SA0DVA,CAAA,CAAY,UAAZ;AAAwB,SAAxB,CA1DU,OAqEZA,CAAA,CAAY,UAAZ,CAAwB,OAAxB,CArEY,UAiFTA,CAAA,CAAY,UAAZ,CAAwB,UAAxB,CAAoC,SAApC,CAjFS,WAmHRA,CAAA,CAAY,kBAAZ,CAAgC,UAAhC,CAnHQ,QA8HXA,CAAA,CAAY,iBAAZ,CAA+B,UAA/B,CA9HW,YA0IPA,CAAA,CAAY,qBAAZ,CAAmC,UAAnC,CA1IO,WAuJRA,CAAA,CAAY,kBAAZ,CAAgC,WAAhC,CAvJQ,QAkKXQ,CAlKW,KA8KdC,QAAQ,CAACC,CAAD,CAAQ,CACnBH,CAAAI,KAAA,CAAeD,CAAf,CACA,OAAO,KAFY,CA9KF,CAoLjBb,EAAJ,EACEW,CAAA,CAAOX,CAAP,CAGF,OAAQS,EAxM8B,CAzET,EAyE/B,CAX+C,CAtDP,CART,EAQnC,CAdiC,CAAnCpB,CA2SA,CAAkBC,MAAlB,CA/XY,CAAX,CAAA,CAgYEA,MAhYF;", |
||||
"sources":["angular-loader.js","MINERR_ASSET"], |
||||
"names":["minErr","setupModuleLoader","window","$injectorMinErr","ngMinErr","angular","$$minErr","factory","modules","module","name","requires","configFn","context","hasOwnProperty","invokeLater","provider","method","insertMethod","invokeQueue","arguments","moduleInstance","runBlocks","config","run","block","push"] |
||||
} |
@ -0,0 +1,546 @@
|
||||
/** |
||||
* @license AngularJS v1.2.3 |
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* License: MIT |
||||
*/ |
||||
(function(window, angular, undefined) {'use strict'; |
||||
|
||||
var $resourceMinErr = angular.$$minErr('$resource'); |
||||
|
||||
// Helper functions and regex to lookup a dotted path on an object
|
||||
// stopping at undefined/null. The path must be composed of ASCII
|
||||
// identifiers (just like $parse)
|
||||
var MEMBER_NAME_REGEX = /^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/; |
||||
|
||||
function isValidDottedPath(path) { |
||||
return (path != null && path !== '' && path !== 'hasOwnProperty' && |
||||
MEMBER_NAME_REGEX.test('.' + path)); |
||||
} |
||||
|
||||
function lookupDottedPath(obj, path) { |
||||
if (!isValidDottedPath(path)) { |
||||
throw $resourceMinErr('badmember', 'Dotted member path "@{0}" is invalid.', path); |
||||
} |
||||
var keys = path.split('.'); |
||||
for (var i = 0, ii = keys.length; i < ii && obj !== undefined; i++) { |
||||
var key = keys[i]; |
||||
obj = (obj !== null) ? obj[key] : undefined; |
||||
} |
||||
return obj; |
||||
} |
||||
|
||||
/** |
||||
* @ngdoc overview |
||||
* @name ngResource |
||||
* @description |
||||
* |
||||
* # ngResource |
||||
* |
||||
* The `ngResource` module provides interaction support with RESTful services |
||||
* via the $resource service. |
||||
* |
||||
* {@installModule resource} |
||||
* |
||||
* <div doc-module-components="ngResource"></div> |
||||
* |
||||
* See {@link ngResource.$resource `$resource`} for usage. |
||||
*/ |
||||
|
||||
/** |
||||
* @ngdoc object |
||||
* @name ngResource.$resource |
||||
* @requires $http |
||||
* |
||||
* @description |
||||
* A factory which creates a resource object that lets you interact with |
||||
* [RESTful](http://en.wikipedia.org/wiki/Representational_State_Transfer) server-side data sources.
|
||||
* |
||||
* The returned resource object has action methods which provide high-level behaviors without |
||||
* the need to interact with the low level {@link ng.$http $http} service. |
||||
* |
||||
* Requires the {@link ngResource `ngResource`} module to be installed. |
||||
* |
||||
* @param {string} url A parametrized URL template with parameters prefixed by `:` as in |
||||
* `/user/:username`. If you are using a URL with a port number (e.g. |
||||
* `http://example.com:8080/api`), it will be respected. |
||||
* |
||||
* If you are using a url with a suffix, just add the suffix, like this: |
||||
* `$resource('http://example.com/resource.json')` or `$resource('http://example.com/:id.json')` |
||||
* or even `$resource('http://example.com/resource/:resource_id.:format')` |
||||
* If the parameter before the suffix is empty, :resource_id in this case, then the `/.` will be |
||||
* collapsed down to a single `.`. If you need this sequence to appear and not collapse then you |
||||
* can escape it with `/\.`. |
||||
* |
||||
* @param {Object=} paramDefaults Default values for `url` parameters. These can be overridden in |
||||
* `actions` methods. If any of the parameter value is a function, it will be executed every time |
||||
* when a param value needs to be obtained for a request (unless the param was overridden). |
||||
* |
||||
* Each key value in the parameter object is first bound to url template if present and then any |
||||
* excess keys are appended to the url search query after the `?`. |
||||
* |
||||
* Given a template `/path/:verb` and parameter `{verb:'greet', salutation:'Hello'}` results in |
||||
* URL `/path/greet?salutation=Hello`. |
||||
* |
||||
* If the parameter value is prefixed with `@` then the value of that parameter is extracted from |
||||
* the data object (useful for non-GET operations). |
||||
* |
||||
* @param {Object.<Object>=} actions Hash with declaration of custom action that should extend the |
||||
* default set of resource actions. The declaration should be created in the format of {@link |
||||
* ng.$http#usage_parameters $http.config}: |
||||
* |
||||
* {action1: {method:?, params:?, isArray:?, headers:?, ...}, |
||||
* action2: {method:?, params:?, isArray:?, headers:?, ...}, |
||||
* ...} |
||||
* |
||||
* Where: |
||||
* |
||||
* - **`action`** – {string} – The name of action. This name becomes the name of the method on |
||||
* your resource object. |
||||
* - **`method`** – {string} – HTTP request method. Valid methods are: `GET`, `POST`, `PUT`, |
||||
* `DELETE`, and `JSONP`. |
||||
* - **`params`** – {Object=} – Optional set of pre-bound parameters for this action. If any of |
||||
* the parameter value is a function, it will be executed every time when a param value needs to |
||||
* be obtained for a request (unless the param was overridden). |
||||
* - **`url`** – {string} – action specific `url` override. The url templating is supported just |
||||
* like for the resource-level urls. |
||||
* - **`isArray`** – {boolean=} – If true then the returned object for this action is an array, |
||||
* see `returns` section. |
||||
* - **`transformRequest`** – |
||||
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` – |
||||
* transform function or an array of such functions. The transform function takes the http |
||||
* request body and headers and returns its transformed (typically serialized) version. |
||||
* - **`transformResponse`** – |
||||
* `{function(data, headersGetter)|Array.<function(data, headersGetter)>}` – |
||||
* transform function or an array of such functions. The transform function takes the http |
||||
* response body and headers and returns its transformed (typically deserialized) version. |
||||
* - **`cache`** – `{boolean|Cache}` – If true, a default $http cache will be used to cache the |
||||
* GET request, otherwise if a cache instance built with |
||||
* {@link ng.$cacheFactory $cacheFactory}, this cache will be used for |
||||
* caching. |
||||
* - **`timeout`** – `{number|Promise}` – timeout in milliseconds, or {@link ng.$q promise} that |
||||
* should abort the request when resolved. |
||||
* - **`withCredentials`** - `{boolean}` - whether to set the `withCredentials` flag on the |
||||
* XHR object. See {@link https://developer.mozilla.org/en/http_access_control#section_5
|
||||
* requests with credentials} for more information. |
||||
* - **`responseType`** - `{string}` - see {@link |
||||
* https://developer.mozilla.org/en-US/docs/DOM/XMLHttpRequest#responseType requestType}.
|
||||
* - **`interceptor`** - `{Object=}` - The interceptor object has two optional methods - |
||||
* `response` and `responseError`. Both `response` and `responseError` interceptors get called |
||||
* with `http response` object. See {@link ng.$http $http interceptors}. |
||||
* |
||||
* @returns {Object} A resource "class" object with methods for the default set of resource actions |
||||
* optionally extended with custom `actions`. The default set contains these actions: |
||||
* |
||||
* { 'get': {method:'GET'}, |
||||
* 'save': {method:'POST'}, |
||||
* 'query': {method:'GET', isArray:true}, |
||||
* 'remove': {method:'DELETE'}, |
||||
* 'delete': {method:'DELETE'} }; |
||||
* |
||||
* Calling these methods invoke an {@link ng.$http} with the specified http method, |
||||
* destination and parameters. When the data is returned from the server then the object is an |
||||
* instance of the resource class. The actions `save`, `remove` and `delete` are available on it |
||||
* as methods with the `$` prefix. This allows you to easily perform CRUD operations (create, |
||||
* read, update, delete) on server-side data like this: |
||||
* <pre> |
||||
var User = $resource('/user/:userId', {userId:'@id'}); |
||||
var user = User.get({userId:123}, function() { |
||||
user.abc = true; |
||||
user.$save(); |
||||
}); |
||||
</pre> |
||||
* |
||||
* It is important to realize that invoking a $resource object method immediately returns an |
||||
* empty reference (object or array depending on `isArray`). Once the data is returned from the |
||||
* server the existing reference is populated with the actual data. This is a useful trick since |
||||
* usually the resource is assigned to a model which is then rendered by the view. Having an empty |
||||
* object results in no rendering, once the data arrives from the server then the object is |
||||
* populated with the data and the view automatically re-renders itself showing the new data. This |
||||
* means that in most cases one never has to write a callback function for the action methods. |
||||
* |
||||
* The action methods on the class object or instance object can be invoked with the following |
||||
* parameters: |
||||
* |
||||
* - HTTP GET "class" actions: `Resource.action([parameters], [success], [error])` |
||||
* - non-GET "class" actions: `Resource.action([parameters], postData, [success], [error])` |
||||
* - non-GET instance actions: `instance.$action([parameters], [success], [error])` |
||||
* |
||||
* Success callback is called with (value, responseHeaders) arguments. Error callback is called |
||||
* with (httpResponse) argument. |
||||
* |
||||
* Class actions return empty instance (with additional properties below). |
||||
* Instance actions return promise of the action. |
||||
* |
||||
* The Resource instances and collection have these additional properties: |
||||
* |
||||
* - `$promise`: the {@link ng.$q promise} of the original server interaction that created this |
||||
* instance or collection. |
||||
* |
||||
* On success, the promise is resolved with the same resource instance or collection object, |
||||
* updated with data from server. This makes it easy to use in |
||||
* {@link ngRoute.$routeProvider resolve section of $routeProvider.when()} to defer view |
||||
* rendering until the resource(s) are loaded. |
||||
* |
||||
* On failure, the promise is resolved with the {@link ng.$http http response} object, without |
||||
* the `resource` property. |
||||
* |
||||
* - `$resolved`: `true` after first server interaction is completed (either with success or |
||||
* rejection), `false` before that. Knowing if the Resource has been resolved is useful in |
||||
* data-binding. |
||||
* |
||||
* @example |
||||
* |
||||
* # Credit card resource |
||||
* |
||||
* <pre> |
||||
// Define CreditCard class
|
||||
var CreditCard = $resource('/user/:userId/card/:cardId', |
||||
{userId:123, cardId:'@id'}, { |
||||
charge: {method:'POST', params:{charge:true}} |
||||
}); |
||||
|
||||
// We can retrieve a collection from the server
|
||||
var cards = CreditCard.query(function() { |
||||
// GET: /user/123/card
|
||||
// server returns: [ {id:456, number:'1234', name:'Smith'} ];
|
||||
|
||||
var card = cards[0]; |
||||
// each item is an instance of CreditCard
|
||||
expect(card instanceof CreditCard).toEqual(true); |
||||
card.name = "J. Smith"; |
||||
// non GET methods are mapped onto the instances
|
||||
card.$save(); |
||||
// POST: /user/123/card/456 {id:456, number:'1234', name:'J. Smith'}
|
||||
// server returns: {id:456, number:'1234', name: 'J. Smith'};
|
||||
|
||||
// our custom method is mapped as well.
|
||||
card.$charge({amount:9.99}); |
||||
// POST: /user/123/card/456?amount=9.99&charge=true {id:456, number:'1234', name:'J. Smith'}
|
||||
}); |
||||
|
||||
// we can create an instance as well
|
||||
var newCard = new CreditCard({number:'0123'}); |
||||
newCard.name = "Mike Smith"; |
||||
newCard.$save(); |
||||
// POST: /user/123/card {number:'0123', name:'Mike Smith'}
|
||||
// server returns: {id:789, number:'01234', name: 'Mike Smith'};
|
||||
expect(newCard.id).toEqual(789); |
||||
* </pre> |
||||
* |
||||
* The object returned from this function execution is a resource "class" which has "static" method |
||||
* for each action in the definition. |
||||
* |
||||
* Calling these methods invoke `$http` on the `url` template with the given `method`, `params` and |
||||
* `headers`. |
||||
* When the data is returned from the server then the object is an instance of the resource type and |
||||
* all of the non-GET methods are available with `$` prefix. This allows you to easily support CRUD |
||||
* operations (create, read, update, delete) on server-side data. |
||||
|
||||
<pre> |
||||
var User = $resource('/user/:userId', {userId:'@id'}); |
||||
var user = User.get({userId:123}, function() { |
||||
user.abc = true; |
||||
user.$save(); |
||||
}); |
||||
</pre> |
||||
* |
||||
* It's worth noting that the success callback for `get`, `query` and other methods gets passed |
||||
* in the response that came from the server as well as $http header getter function, so one |
||||
* could rewrite the above example and get access to http headers as: |
||||
* |
||||
<pre> |
||||
var User = $resource('/user/:userId', {userId:'@id'}); |
||||
User.get({userId:123}, function(u, getResponseHeaders){ |
||||
u.abc = true; |
||||
u.$save(function(u, putResponseHeaders) { |
||||
//u => saved user object
|
||||
//putResponseHeaders => $http header getter
|
||||
}); |
||||
}); |
||||
</pre> |
||||
*/ |
||||
angular.module('ngResource', ['ng']). |
||||
factory('$resource', ['$http', '$q', function($http, $q) { |
||||
|
||||
var DEFAULT_ACTIONS = { |
||||
'get': {method:'GET'}, |
||||
'save': {method:'POST'}, |
||||
'query': {method:'GET', isArray:true}, |
||||
'remove': {method:'DELETE'}, |
||||
'delete': {method:'DELETE'} |
||||
}; |
||||
var noop = angular.noop, |
||||
forEach = angular.forEach, |
||||
extend = angular.extend, |
||||
copy = angular.copy, |
||||
isFunction = angular.isFunction; |
||||
|
||||
/** |
||||
* We need our custom method because encodeURIComponent is too aggressive and doesn't follow |
||||
* http://www.ietf.org/rfc/rfc3986.txt with regards to the character set (pchar) allowed in path
|
||||
* segments: |
||||
* segment = *pchar |
||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@" |
||||
* pct-encoded = "%" HEXDIG HEXDIG |
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" |
||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")" |
||||
* / "*" / "+" / "," / ";" / "=" |
||||
*/ |
||||
function encodeUriSegment(val) { |
||||
return encodeUriQuery(val, true). |
||||
replace(/%26/gi, '&'). |
||||
replace(/%3D/gi, '='). |
||||
replace(/%2B/gi, '+'); |
||||
} |
||||
|
||||
|
||||
/** |
||||
* This method is intended for encoding *key* or *value* parts of query component. We need a |
||||
* custom method because encodeURIComponent is too aggressive and encodes stuff that doesn't |
||||
* have to be encoded per http://tools.ietf.org/html/rfc3986:
|
||||
* query = *( pchar / "/" / "?" ) |
||||
* pchar = unreserved / pct-encoded / sub-delims / ":" / "@" |
||||
* unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" |
||||
* pct-encoded = "%" HEXDIG HEXDIG |
||||
* sub-delims = "!" / "$" / "&" / "'" / "(" / ")" |
||||
* / "*" / "+" / "," / ";" / "=" |
||||
*/ |
||||
function encodeUriQuery(val, pctEncodeSpaces) { |
||||
return encodeURIComponent(val). |
||||
replace(/%40/gi, '@'). |
||||
replace(/%3A/gi, ':'). |
||||
replace(/%24/g, '$'). |
||||
replace(/%2C/gi, ','). |
||||
replace(/%20/g, (pctEncodeSpaces ? '%20' : '+')); |
||||
} |
||||
|
||||
function Route(template, defaults) { |
||||
this.template = template; |
||||
this.defaults = defaults || {}; |
||||
this.urlParams = {}; |
||||
} |
||||
|
||||
Route.prototype = { |
||||
setUrlParams: function(config, params, actionUrl) { |
||||
var self = this, |
||||
url = actionUrl || self.template, |
||||
val, |
||||
encodedVal; |
||||
|
||||
var urlParams = self.urlParams = {}; |
||||
forEach(url.split(/\W/), function(param){ |
||||
if (param === 'hasOwnProperty') { |
||||
throw $resourceMinErr('badname', "hasOwnProperty is not a valid parameter name."); |
||||
} |
||||
if (!(new RegExp("^\\d+$").test(param)) && param && |
||||
(new RegExp("(^|[^\\\\]):" + param + "(\\W|$)").test(url))) { |
||||
urlParams[param] = true; |
||||
} |
||||
}); |
||||
url = url.replace(/\\:/g, ':'); |
||||
|
||||
params = params || {}; |
||||
forEach(self.urlParams, function(_, urlParam){ |
||||
val = params.hasOwnProperty(urlParam) ? params[urlParam] : self.defaults[urlParam]; |
||||
if (angular.isDefined(val) && val !== null) { |
||||
encodedVal = encodeUriSegment(val); |
||||
url = url.replace(new RegExp(":" + urlParam + "(\\W|$)", "g"), encodedVal + "$1"); |
||||
} else { |
||||
url = url.replace(new RegExp("(\/?):" + urlParam + "(\\W|$)", "g"), function(match, |
||||
leadingSlashes, tail) { |
||||
if (tail.charAt(0) == '/') { |
||||
return tail; |
||||
} else { |
||||
return leadingSlashes + tail; |
||||
} |
||||
}); |
||||
} |
||||
}); |
||||
|
||||
// strip trailing slashes and set the url
|
||||
url = url.replace(/\/+$/, ''); |
||||
// then replace collapse `/.` if found in the last URL path segment before the query
|
||||
// E.g. `http://url.com/id./format?q=x` becomes `http://url.com/id.format?q=x`
|
||||
url = url.replace(/\/\.(?=\w+($|\?))/, '.'); |
||||
// replace escaped `/\.` with `/.`
|
||||
config.url = url.replace(/\/\\\./, '/.'); |
||||
|
||||
|
||||
// set params - delegate param encoding to $http
|
||||
forEach(params, function(value, key){ |
||||
if (!self.urlParams[key]) { |
||||
config.params = config.params || {}; |
||||
config.params[key] = value; |
||||
} |
||||
}); |
||||
} |
||||
}; |
||||
|
||||
|
||||
function resourceFactory(url, paramDefaults, actions) { |
||||
var route = new Route(url); |
||||
|
||||
actions = extend({}, DEFAULT_ACTIONS, actions); |
||||
|
||||
function extractParams(data, actionParams){ |
||||
var ids = {}; |
||||
actionParams = extend({}, paramDefaults, actionParams); |
||||
forEach(actionParams, function(value, key){ |
||||
if (isFunction(value)) { value = value(); } |
||||
ids[key] = value && value.charAt && value.charAt(0) == '@' ? |
||||
lookupDottedPath(data, value.substr(1)) : value; |
||||
}); |
||||
return ids; |
||||
} |
||||
|
||||
function defaultResponseInterceptor(response) { |
||||
return response.resource; |
||||
} |
||||
|
||||
function Resource(value){ |
||||
copy(value || {}, this); |
||||
} |
||||
|
||||
forEach(actions, function(action, name) { |
||||
var hasBody = /^(POST|PUT|PATCH)$/i.test(action.method); |
||||
|
||||
Resource[name] = function(a1, a2, a3, a4) { |
||||
var params = {}, data, success, error; |
||||
|
||||
/* jshint -W086 */ /* (purposefully fall through case statements) */ |
||||
switch(arguments.length) { |
||||
case 4: |
||||
error = a4; |
||||
success = a3; |
||||
//fallthrough
|
||||
case 3: |
||||
case 2: |
||||
if (isFunction(a2)) { |
||||
if (isFunction(a1)) { |
||||
success = a1; |
||||
error = a2; |
||||
break; |
||||
} |
||||
|
||||
success = a2; |
||||
error = a3; |
||||
//fallthrough
|
||||
} else { |
||||
params = a1; |
||||
data = a2; |
||||
success = a3; |
||||
break; |
||||
} |
||||
case 1: |
||||
if (isFunction(a1)) success = a1; |
||||
else if (hasBody) data = a1; |
||||
else params = a1; |
||||
break; |
||||
case 0: break; |
||||
default: |
||||
throw $resourceMinErr('badargs', |
||||
"Expected up to 4 arguments [params, data, success, error], got {0} arguments", |
||||
arguments.length); |
||||
} |
||||
/* jshint +W086 */ /* (purposefully fall through case statements) */ |
||||
|
||||
var isInstanceCall = this instanceof Resource; |
||||
var value = isInstanceCall ? data : (action.isArray ? [] : new Resource(data)); |
||||
var httpConfig = {}; |
||||
var responseInterceptor = action.interceptor && action.interceptor.response || |
||||
defaultResponseInterceptor; |
||||
var responseErrorInterceptor = action.interceptor && action.interceptor.responseError || |
||||
undefined; |
||||
|
||||
forEach(action, function(value, key) { |
||||
if (key != 'params' && key != 'isArray' && key != 'interceptor') { |
||||
httpConfig[key] = copy(value); |
||||
} |
||||
}); |
||||
|
||||
if (hasBody) httpConfig.data = data; |
||||
route.setUrlParams(httpConfig, |
||||
extend({}, extractParams(data, action.params || {}), params), |
||||
action.url); |
||||
|
||||
var promise = $http(httpConfig).then(function(response) { |
||||
var data = response.data, |
||||
promise = value.$promise; |
||||
|
||||
if (data) { |
||||
// Need to convert action.isArray to boolean in case it is undefined
|
||||
// jshint -W018
|
||||
if ( angular.isArray(data) !== (!!action.isArray) ) { |
||||
throw $resourceMinErr('badcfg', 'Error in resource configuration. Expected ' + |
||||
'response to contain an {0} but got an {1}', |
||||
action.isArray?'array':'object', angular.isArray(data)?'array':'object'); |
||||
} |
||||
// jshint +W018
|
||||
if (action.isArray) { |
||||
value.length = 0; |
||||
forEach(data, function(item) { |
||||
value.push(new Resource(item)); |
||||
}); |
||||
} else { |
||||
copy(data, value); |
||||
value.$promise = promise; |
||||
} |
||||
} |
||||
|
||||
value.$resolved = true; |
||||
|
||||
response.resource = value; |
||||
|
||||
return response; |
||||
}, function(response) { |
||||
value.$resolved = true; |
||||
|
||||
(error||noop)(response); |
||||
|
||||
return $q.reject(response); |
||||
}); |
||||
|
||||
promise = promise.then( |
||||
function(response) { |
||||
var value = responseInterceptor(response); |
||||
(success||noop)(value, response.headers); |
||||
return value; |
||||
}, |
||||
responseErrorInterceptor); |
||||
|
||||
if (!isInstanceCall) { |
||||
// we are creating instance / collection
|
||||
// - set the initial promise
|
||||
// - return the instance / collection
|
||||
value.$promise = promise; |
||||
value.$resolved = false; |
||||
|
||||
return value; |
||||
} |
||||
|
||||
// instance call
|
||||
return promise; |
||||
}; |
||||
|
||||
|
||||
Resource.prototype['$' + name] = function(params, success, error) { |
||||
if (isFunction(params)) { |
||||
error = success; success = params; params = {}; |
||||
} |
||||
var result = Resource[name].call(this, params, this, success, error); |
||||
return result.$promise || result; |
||||
}; |
||||
}); |
||||
|
||||
Resource.bind = function(additionalParamDefaults){ |
||||
return resourceFactory(url, extend({}, paramDefaults, additionalParamDefaults), actions); |
||||
}; |
||||
|
||||
return Resource; |
||||
} |
||||
|
||||
return resourceFactory; |
||||
}]); |
||||
|
||||
|
||||
})(window, window.angular); |
@ -0,0 +1,12 @@
|
||||
/* |
||||
AngularJS v1.2.3 |
||||
(c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
License: MIT |
||||
*/ |
||||
(function(H,f,z){'use strict';var u=f.$$minErr("$resource"),A=/^(\.[a-zA-Z_$][0-9a-zA-Z_$]*)+$/;f.module("ngResource",["ng"]).factory("$resource",["$http","$q",function(D,E){function n(f,h){this.template=f;this.defaults=h||{};this.urlParams={}}function v(m,h,k){function r(d,c){var e={};c=w({},h,c);s(c,function(a,c){t(a)&&(a=a());var g;if(a&&a.charAt&&"@"==a.charAt(0)){g=d;var b=a.substr(1);if(null==b||""===b||"hasOwnProperty"===b||!A.test("."+b))throw u("badmember",b);for(var b=b.split("."),f=0,h= |
||||
b.length;f<h&&g!==z;f++){var q=b[f];g=null!==g?g[q]:z}}else g=a;e[c]=g});return e}function e(b){return b.resource}function b(b){B(b||{},this)}var F=new n(m);k=w({},G,k);s(k,function(d,c){var h=/^(POST|PUT|PATCH)$/i.test(d.method);b[c]=function(a,c,g,m){var p={},k,q,x;switch(arguments.length){case 4:x=m,q=g;case 3:case 2:if(t(c)){if(t(a)){q=a;x=c;break}q=c;x=g}else{p=a;k=c;q=g;break}case 1:t(a)?q=a:h?k=a:p=a;break;case 0:break;default:throw u("badargs",arguments.length);}var n=this instanceof b,l= |
||||
n?k:d.isArray?[]:new b(k),y={},v=d.interceptor&&d.interceptor.response||e,A=d.interceptor&&d.interceptor.responseError||z;s(d,function(b,a){"params"!=a&&("isArray"!=a&&"interceptor"!=a)&&(y[a]=B(b))});h&&(y.data=k);F.setUrlParams(y,w({},r(k,d.params||{}),p),d.url);p=D(y).then(function(a){var c=a.data,g=l.$promise;if(c){if(f.isArray(c)!==!!d.isArray)throw u("badcfg",d.isArray?"array":"object",f.isArray(c)?"array":"object");d.isArray?(l.length=0,s(c,function(a){l.push(new b(a))})):(B(c,l),l.$promise= |
||||
g)}l.$resolved=!0;a.resource=l;return a},function(a){l.$resolved=!0;(x||C)(a);return E.reject(a)});p=p.then(function(a){var c=v(a);(q||C)(c,a.headers);return c},A);return n?p:(l.$promise=p,l.$resolved=!1,l)};b.prototype["$"+c]=function(a,d,g){t(a)&&(g=d,d=a,a={});a=b[c].call(this,a,this,d,g);return a.$promise||a}});b.bind=function(b){return v(m,w({},h,b),k)};return b}var G={get:{method:"GET"},save:{method:"POST"},query:{method:"GET",isArray:!0},remove:{method:"DELETE"},"delete":{method:"DELETE"}}, |
||||
C=f.noop,s=f.forEach,w=f.extend,B=f.copy,t=f.isFunction;n.prototype={setUrlParams:function(m,h,k){var r=this,e=k||r.template,b,n,d=r.urlParams={};s(e.split(/\W/),function(c){if("hasOwnProperty"===c)throw u("badname");!/^\d+$/.test(c)&&(c&&RegExp("(^|[^\\\\]):"+c+"(\\W|$)").test(e))&&(d[c]=!0)});e=e.replace(/\\:/g,":");h=h||{};s(r.urlParams,function(c,d){b=h.hasOwnProperty(d)?h[d]:r.defaults[d];f.isDefined(b)&&null!==b?(n=encodeURIComponent(b).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g, |
||||
"$").replace(/%2C/gi,",").replace(/%20/g,"%20").replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+"),e=e.replace(RegExp(":"+d+"(\\W|$)","g"),n+"$1")):e=e.replace(RegExp("(/?):"+d+"(\\W|$)","g"),function(a,c,b){return"/"==b.charAt(0)?b:c+b})});e=e.replace(/\/+$/,"");e=e.replace(/\/\.(?=\w+($|\?))/,".");m.url=e.replace(/\/\\\./,"/.");s(h,function(b,d){r.urlParams[d]||(m.params=m.params||{},m.params[d]=b)})}};return v}])})(window,window.angular); |
||||
//# sourceMappingURL=angular-resource.min.js.map
|
@ -0,0 +1,891 @@
|
||||
/** |
||||
* @license AngularJS v1.2.3 |
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* License: MIT |
||||
*/ |
||||
(function(window, angular, undefined) {'use strict'; |
||||
|
||||
/** |
||||
* @ngdoc overview |
||||
* @name ngRoute |
||||
* @description |
||||
* |
||||
* # ngRoute |
||||
* |
||||
* The `ngRoute` module provides routing and deeplinking services and directives for angular apps. |
||||
* |
||||
* ## Example |
||||
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. |
||||
*
|
||||
* {@installModule route} |
||||
* |
||||
* <div doc-module-components="ngRoute"></div> |
||||
*/ |
||||
/* global -ngRouteModule */ |
||||
var ngRouteModule = angular.module('ngRoute', ['ng']). |
||||
provider('$route', $RouteProvider); |
||||
|
||||
/** |
||||
* @ngdoc object |
||||
* @name ngRoute.$routeProvider |
||||
* @function |
||||
* |
||||
* @description |
||||
* |
||||
* Used for configuring routes. |
||||
*
|
||||
* ## Example |
||||
* See {@link ngRoute.$route#example $route} for an example of configuring and using `ngRoute`. |
||||
* |
||||
* ## Dependencies |
||||
* Requires the {@link ngRoute `ngRoute`} module to be installed. |
||||
*/ |
||||
function $RouteProvider(){ |
||||
function inherit(parent, extra) { |
||||
return angular.extend(new (angular.extend(function() {}, {prototype:parent}))(), extra); |
||||
} |
||||
|
||||
var routes = {}; |
||||
|
||||
/** |
||||
* @ngdoc method |
||||
* @name ngRoute.$routeProvider#when |
||||
* @methodOf ngRoute.$routeProvider |
||||
* |
||||
* @param {string} path Route path (matched against `$location.path`). If `$location.path` |
||||
* contains redundant trailing slash or is missing one, the route will still match and the |
||||
* `$location.path` will be updated to add or drop the trailing slash to exactly match the |
||||
* route definition. |
||||
* |
||||
* * `path` can contain named groups starting with a colon (`:name`). All characters up |
||||
* to the next slash are matched and stored in `$routeParams` under the given `name` |
||||
* when the route matches. |
||||
* * `path` can contain named groups starting with a colon and ending with a star (`:name*`). |
||||
* All characters are eagerly stored in `$routeParams` under the given `name` |
||||
* when the route matches. |
||||
* * `path` can contain optional named groups with a question mark (`:name?`). |
||||
* |
||||
* For example, routes like `/color/:color/largecode/:largecode*\/edit` will match |
||||
* `/color/brown/largecode/code/with/slashs/edit` and extract: |
||||
* |
||||
* * `color: brown` |
||||
* * `largecode: code/with/slashs`. |
||||
* |
||||
* |
||||
* @param {Object} route Mapping information to be assigned to `$route.current` on route |
||||
* match. |
||||
* |
||||
* Object properties: |
||||
* |
||||
* - `controller` – `{(string|function()=}` – Controller fn that should be associated with |
||||
* newly created scope or the name of a {@link angular.Module#controller registered |
||||
* controller} if passed as a string. |
||||
* - `controllerAs` – `{string=}` – A controller alias name. If present the controller will be |
||||
* published to scope under the `controllerAs` name. |
||||
* - `template` – `{string=|function()=}` – html template as a string or a function that |
||||
* returns an html template as a string which should be used by {@link |
||||
* ngRoute.directive:ngView ngView} or {@link ng.directive:ngInclude ngInclude} directives. |
||||
* This property takes precedence over `templateUrl`. |
||||
* |
||||
* If `template` is a function, it will be called with the following parameters: |
||||
* |
||||
* - `{Array.<Object>}` - route parameters extracted from the current |
||||
* `$location.path()` by applying the current route |
||||
* |
||||
* - `templateUrl` – `{string=|function()=}` – path or function that returns a path to an html |
||||
* template that should be used by {@link ngRoute.directive:ngView ngView}. |
||||
* |
||||
* If `templateUrl` is a function, it will be called with the following parameters: |
||||
* |
||||
* - `{Array.<Object>}` - route parameters extracted from the current |
||||
* `$location.path()` by applying the current route |
||||
* |
||||
* - `resolve` - `{Object.<string, function>=}` - An optional map of dependencies which should |
||||
* be injected into the controller. If any of these dependencies are promises, the router |
||||
* will wait for them all to be resolved or one to be rejected before the controller is |
||||
* instantiated. |
||||
* If all the promises are resolved successfully, the values of the resolved promises are |
||||
* injected and {@link ngRoute.$route#$routeChangeSuccess $routeChangeSuccess} event is |
||||
* fired. If any of the promises are rejected the |
||||
* {@link ngRoute.$route#$routeChangeError $routeChangeError} event is fired. The map object |
||||
* is: |
||||
* |
||||
* - `key` – `{string}`: a name of a dependency to be injected into the controller. |
||||
* - `factory` - `{string|function}`: If `string` then it is an alias for a service. |
||||
* Otherwise if function, then it is {@link api/AUTO.$injector#invoke injected} |
||||
* and the return value is treated as the dependency. If the result is a promise, it is |
||||
* resolved before its value is injected into the controller. Be aware that |
||||
* `ngRoute.$routeParams` will still refer to the previous route within these resolve |
||||
* functions. Use `$route.current.params` to access the new route parameters, instead. |
||||
* |
||||
* - `redirectTo` – {(string|function())=} – value to update |
||||
* {@link ng.$location $location} path with and trigger route redirection. |
||||
* |
||||
* If `redirectTo` is a function, it will be called with the following parameters: |
||||
* |
||||
* - `{Object.<string>}` - route parameters extracted from the current |
||||
* `$location.path()` by applying the current route templateUrl. |
||||
* - `{string}` - current `$location.path()` |
||||
* - `{Object}` - current `$location.search()` |
||||
* |
||||
* The custom `redirectTo` function is expected to return a string which will be used |
||||
* to update `$location.path()` and `$location.search()`. |
||||
* |
||||
* - `[reloadOnSearch=true]` - {boolean=} - reload route when only `$location.search()` |
||||
* or `$location.hash()` changes. |
||||
* |
||||
* If the option is set to `false` and url in the browser changes, then |
||||
* `$routeUpdate` event is broadcasted on the root scope. |
||||
* |
||||
* - `[caseInsensitiveMatch=false]` - {boolean=} - match routes without being case sensitive |
||||
* |
||||
* If the option is set to `true`, then the particular route can be matched without being |
||||
* case sensitive |
||||
* |
||||
* @returns {Object} self |
||||
* |
||||
* @description |
||||
* Adds a new route definition to the `$route` service. |
||||
*/ |
||||
this.when = function(path, route) { |
||||
routes[path] = angular.extend( |
||||
{reloadOnSearch: true}, |
||||
route, |
||||
path && pathRegExp(path, route) |
||||
); |
||||
|
||||
// create redirection for trailing slashes
|
||||
if (path) { |
||||
var redirectPath = (path[path.length-1] == '/') |
||||
? path.substr(0, path.length-1) |
||||
: path +'/'; |
||||
|
||||
routes[redirectPath] = angular.extend( |
||||
{redirectTo: path}, |
||||
pathRegExp(redirectPath, route) |
||||
); |
||||
} |
||||
|
||||
return this; |
||||
}; |
||||
|
||||
/** |
||||
* @param path {string} path |
||||
* @param opts {Object} options |
||||
* @return {?Object} |
||||
* |
||||
* @description |
||||
* Normalizes the given path, returning a regular expression |
||||
* and the original path. |
||||
* |
||||
* Inspired by pathRexp in visionmedia/express/lib/utils.js. |
||||
*/ |
||||
function pathRegExp(path, opts) { |
||||
var insensitive = opts.caseInsensitiveMatch, |
||||
ret = { |
||||
originalPath: path, |
||||
regexp: path |
||||
}, |
||||
keys = ret.keys = []; |
||||
|
||||
path = path |
||||
.replace(/([().])/g, '\\$1') |
||||
.replace(/(\/)?:(\w+)([\?|\*])?/g, function(_, slash, key, option){ |
||||
var optional = option === '?' ? option : null; |
||||
var star = option === '*' ? option : null; |
||||
keys.push({ name: key, optional: !!optional }); |
||||
slash = slash || ''; |
||||
return '' |
||||
+ (optional ? '' : slash) |
||||
+ '(?:' |
||||
+ (optional ? slash : '') |
||||
+ (star && '(.+?)' || '([^/]+)') |
||||
+ (optional || '') |
||||
+ ')' |
||||
+ (optional || ''); |
||||
}) |
||||
.replace(/([\/$\*])/g, '\\$1'); |
||||
|
||||
ret.regexp = new RegExp('^' + path + '$', insensitive ? 'i' : ''); |
||||
return ret; |
||||
} |
||||
|
||||
/** |
||||
* @ngdoc method |
||||
* @name ngRoute.$routeProvider#otherwise |
||||
* @methodOf ngRoute.$routeProvider |
||||
* |
||||
* @description |
||||
* Sets route definition that will be used on route change when no other route definition |
||||
* is matched. |
||||
* |
||||
* @param {Object} params Mapping information to be assigned to `$route.current`. |
||||
* @returns {Object} self |
||||
*/ |
||||
this.otherwise = function(params) { |
||||
this.when(null, params); |
||||
return this; |
||||
}; |
||||
|
||||
|
||||
this.$get = ['$rootScope', |
||||
'$location', |
||||
'$routeParams', |
||||
'$q', |
||||
'$injector', |
||||
'$http', |
||||
'$templateCache', |
||||
'$sce', |
||||
function($rootScope, $location, $routeParams, $q, $injector, $http, $templateCache, $sce) { |
||||
|
||||
/** |
||||
* @ngdoc object |
||||
* @name ngRoute.$route |
||||
* @requires $location |
||||
* @requires $routeParams |
||||
* |
||||
* @property {Object} current Reference to the current route definition. |
||||
* The route definition contains: |
||||
* |
||||
* - `controller`: The controller constructor as define in route definition. |
||||
* - `locals`: A map of locals which is used by {@link ng.$controller $controller} service for |
||||
* controller instantiation. The `locals` contain |
||||
* the resolved values of the `resolve` map. Additionally the `locals` also contain: |
||||
* |
||||
* - `$scope` - The current route scope. |
||||
* - `$template` - The current route template HTML. |
||||
* |
||||
* @property {Array.<Object>} routes Array of all configured routes. |
||||
* |
||||
* @description |
||||
* `$route` is used for deep-linking URLs to controllers and views (HTML partials). |
||||
* It watches `$location.url()` and tries to map the path to an existing route definition. |
||||
* |
||||
* Requires the {@link ngRoute `ngRoute`} module to be installed. |
||||
* |
||||
* You can define routes through {@link ngRoute.$routeProvider $routeProvider}'s API. |
||||
* |
||||
* The `$route` service is typically used in conjunction with the |
||||
* {@link ngRoute.directive:ngView `ngView`} directive and the |
||||
* {@link ngRoute.$routeParams `$routeParams`} service. |
||||
* |
||||
* @example |
||||
This example shows how changing the URL hash causes the `$route` to match a route against the |
||||
URL, and the `ngView` pulls in the partial. |
||||
|
||||
Note that this example is using {@link ng.directive:script inlined templates} |
||||
to get it working on jsfiddle as well. |
||||
|
||||
<example module="ngViewExample" deps="angular-route.js"> |
||||
<file name="index.html"> |
||||
<div ng-controller="MainCntl"> |
||||
Choose: |
||||
<a href="Book/Moby">Moby</a> | |
||||
<a href="Book/Moby/ch/1">Moby: Ch1</a> | |
||||
<a href="Book/Gatsby">Gatsby</a> | |
||||
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | |
||||
<a href="Book/Scarlet">Scarlet Letter</a><br/> |
||||
|
||||
<div ng-view></div> |
||||
<hr /> |
||||
|
||||
<pre>$location.path() = {{$location.path()}}</pre> |
||||
<pre>$route.current.templateUrl = {{$route.current.templateUrl}}</pre> |
||||
<pre>$route.current.params = {{$route.current.params}}</pre> |
||||
<pre>$route.current.scope.name = {{$route.current.scope.name}}</pre> |
||||
<pre>$routeParams = {{$routeParams}}</pre> |
||||
</div> |
||||
</file> |
||||
|
||||
<file name="book.html"> |
||||
controller: {{name}}<br /> |
||||
Book Id: {{params.bookId}}<br /> |
||||
</file> |
||||
|
||||
<file name="chapter.html"> |
||||
controller: {{name}}<br /> |
||||
Book Id: {{params.bookId}}<br /> |
||||
Chapter Id: {{params.chapterId}} |
||||
</file> |
||||
|
||||
<file name="script.js"> |
||||
angular.module('ngViewExample', ['ngRoute']) |
||||
|
||||
.config(function($routeProvider, $locationProvider) { |
||||
$routeProvider.when('/Book/:bookId', { |
||||
templateUrl: 'book.html', |
||||
controller: BookCntl, |
||||
resolve: { |
||||
// I will cause a 1 second delay
|
||||
delay: function($q, $timeout) { |
||||
var delay = $q.defer(); |
||||
$timeout(delay.resolve, 1000); |
||||
return delay.promise; |
||||
} |
||||
} |
||||
}); |
||||
$routeProvider.when('/Book/:bookId/ch/:chapterId', { |
||||
templateUrl: 'chapter.html', |
||||
controller: ChapterCntl |
||||
}); |
||||
|
||||
// configure html5 to get links working on jsfiddle
|
||||
$locationProvider.html5Mode(true); |
||||
}); |
||||
|
||||
function MainCntl($scope, $route, $routeParams, $location) { |
||||
$scope.$route = $route; |
||||
$scope.$location = $location; |
||||
$scope.$routeParams = $routeParams; |
||||
} |
||||
|
||||
function BookCntl($scope, $routeParams) { |
||||
$scope.name = "BookCntl"; |
||||
$scope.params = $routeParams; |
||||
} |
||||
|
||||
function ChapterCntl($scope, $routeParams) { |
||||
$scope.name = "ChapterCntl"; |
||||
$scope.params = $routeParams; |
||||
} |
||||
</file> |
||||
|
||||
<file name="scenario.js"> |
||||
it('should load and compile correct template', function() { |
||||
element('a:contains("Moby: Ch1")').click(); |
||||
var content = element('.doc-example-live [ng-view]').text(); |
||||
expect(content).toMatch(/controller\: ChapterCntl/); |
||||
expect(content).toMatch(/Book Id\: Moby/); |
||||
expect(content).toMatch(/Chapter Id\: 1/); |
||||
|
||||
element('a:contains("Scarlet")').click(); |
||||
sleep(2); // promises are not part of scenario waiting
|
||||
content = element('.doc-example-live [ng-view]').text(); |
||||
expect(content).toMatch(/controller\: BookCntl/); |
||||
expect(content).toMatch(/Book Id\: Scarlet/); |
||||
}); |
||||
</file> |
||||
</example> |
||||
*/ |
||||
|
||||
/** |
||||
* @ngdoc event |
||||
* @name ngRoute.$route#$routeChangeStart |
||||
* @eventOf ngRoute.$route |
||||
* @eventType broadcast on root scope |
||||
* @description |
||||
* Broadcasted before a route change. At this point the route services starts |
||||
* resolving all of the dependencies needed for the route change to occurs. |
||||
* Typically this involves fetching the view template as well as any dependencies |
||||
* defined in `resolve` route property. Once all of the dependencies are resolved |
||||
* `$routeChangeSuccess` is fired. |
||||
* |
||||
* @param {Object} angularEvent Synthetic event object. |
||||
* @param {Route} next Future route information. |
||||
* @param {Route} current Current route information. |
||||
*/ |
||||
|
||||
/** |
||||
* @ngdoc event |
||||
* @name ngRoute.$route#$routeChangeSuccess |
||||
* @eventOf ngRoute.$route |
||||
* @eventType broadcast on root scope |
||||
* @description |
||||
* Broadcasted after a route dependencies are resolved. |
||||
* {@link ngRoute.directive:ngView ngView} listens for the directive |
||||
* to instantiate the controller and render the view. |
||||
* |
||||
* @param {Object} angularEvent Synthetic event object. |
||||
* @param {Route} current Current route information. |
||||
* @param {Route|Undefined} previous Previous route information, or undefined if current is |
||||
* first route entered. |
||||
*/ |
||||
|
||||
/** |
||||
* @ngdoc event |
||||
* @name ngRoute.$route#$routeChangeError |
||||
* @eventOf ngRoute.$route |
||||
* @eventType broadcast on root scope |
||||
* @description |
||||
* Broadcasted if any of the resolve promises are rejected. |
||||
* |
||||
* @param {Object} angularEvent Synthetic event object |
||||
* @param {Route} current Current route information. |
||||
* @param {Route} previous Previous route information. |
||||
* @param {Route} rejection Rejection of the promise. Usually the error of the failed promise. |
||||
*/ |
||||
|
||||
/** |
||||
* @ngdoc event |
||||
* @name ngRoute.$route#$routeUpdate |
||||
* @eventOf ngRoute.$route |
||||
* @eventType broadcast on root scope |
||||
* @description |
||||
* |
||||
* The `reloadOnSearch` property has been set to false, and we are reusing the same |
||||
* instance of the Controller. |
||||
*/ |
||||
|
||||
var forceReload = false, |
||||
$route = { |
||||
routes: routes, |
||||
|
||||
/** |
||||
* @ngdoc method |
||||
* @name ngRoute.$route#reload |
||||
* @methodOf ngRoute.$route |
||||
* |
||||
* @description |
||||
* Causes `$route` service to reload the current route even if |
||||
* {@link ng.$location $location} hasn't changed. |
||||
* |
||||
* As a result of that, {@link ngRoute.directive:ngView ngView} |
||||
* creates new scope, reinstantiates the controller. |
||||
*/ |
||||
reload: function() { |
||||
forceReload = true; |
||||
$rootScope.$evalAsync(updateRoute); |
||||
} |
||||
}; |
||||
|
||||
$rootScope.$on('$locationChangeSuccess', updateRoute); |
||||
|
||||
return $route; |
||||
|
||||
/////////////////////////////////////////////////////
|
||||
|
||||
/** |
||||
* @param on {string} current url |
||||
* @param route {Object} route regexp to match the url against |
||||
* @return {?Object} |
||||
* |
||||
* @description |
||||
* Check if the route matches the current url. |
||||
* |
||||
* Inspired by match in |
||||
* visionmedia/express/lib/router/router.js. |
||||
*/ |
||||
function switchRouteMatcher(on, route) { |
||||
var keys = route.keys, |
||||
params = {}; |
||||
|
||||
if (!route.regexp) return null; |
||||
|
||||
var m = route.regexp.exec(on); |
||||
if (!m) return null; |
||||
|
||||
for (var i = 1, len = m.length; i < len; ++i) { |
||||
var key = keys[i - 1]; |
||||
|
||||
var val = 'string' == typeof m[i] |
||||
? decodeURIComponent(m[i]) |
||||
: m[i]; |
||||
|
||||
if (key && val) { |
||||
params[key.name] = val; |
||||
} |
||||
} |
||||
return params; |
||||
} |
||||
|
||||
function updateRoute() { |
||||
var next = parseRoute(), |
||||
last = $route.current; |
||||
|
||||
if (next && last && next.$$route === last.$$route |
||||
&& angular.equals(next.pathParams, last.pathParams) |
||||
&& !next.reloadOnSearch && !forceReload) { |
||||
last.params = next.params; |
||||
angular.copy(last.params, $routeParams); |
||||
$rootScope.$broadcast('$routeUpdate', last); |
||||
} else if (next || last) { |
||||
forceReload = false; |
||||
$rootScope.$broadcast('$routeChangeStart', next, last); |
||||
$route.current = next; |
||||
if (next) { |
||||
if (next.redirectTo) { |
||||
if (angular.isString(next.redirectTo)) { |
||||
$location.path(interpolate(next.redirectTo, next.params)).search(next.params) |
||||
.replace(); |
||||
} else { |
||||
$location.url(next.redirectTo(next.pathParams, $location.path(), $location.search())) |
||||
.replace(); |
||||
} |
||||
} |
||||
} |
||||
|
||||
$q.when(next). |
||||
then(function() { |
||||
if (next) { |
||||
var locals = angular.extend({}, next.resolve), |
||||
template, templateUrl; |
||||
|
||||
angular.forEach(locals, function(value, key) { |
||||
locals[key] = angular.isString(value) ? |
||||
$injector.get(value) : $injector.invoke(value); |
||||
}); |
||||
|
||||
if (angular.isDefined(template = next.template)) { |
||||
if (angular.isFunction(template)) { |
||||
template = template(next.params); |
||||
} |
||||
} else if (angular.isDefined(templateUrl = next.templateUrl)) { |
||||
if (angular.isFunction(templateUrl)) { |
||||
templateUrl = templateUrl(next.params); |
||||
} |
||||
templateUrl = $sce.getTrustedResourceUrl(templateUrl); |
||||
if (angular.isDefined(templateUrl)) { |
||||
next.loadedTemplateUrl = templateUrl; |
||||
template = $http.get(templateUrl, {cache: $templateCache}). |
||||
then(function(response) { return response.data; }); |
||||
} |
||||
} |
||||
if (angular.isDefined(template)) { |
||||
locals['$template'] = template; |
||||
} |
||||
return $q.all(locals); |
||||
} |
||||
}). |
||||
// after route change
|
||||
then(function(locals) { |
||||
if (next == $route.current) { |
||||
if (next) { |
||||
next.locals = locals; |
||||
angular.copy(next.params, $routeParams); |
||||
} |
||||
$rootScope.$broadcast('$routeChangeSuccess', next, last); |
||||
} |
||||
}, function(error) { |
||||
if (next == $route.current) { |
||||
$rootScope.$broadcast('$routeChangeError', next, last, error); |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
|
||||
|
||||
/** |
||||
* @returns the current active route, by matching it against the URL |
||||
*/ |
||||
function parseRoute() { |
||||
// Match a route
|
||||
var params, match; |
||||
angular.forEach(routes, function(route, path) { |
||||
if (!match && (params = switchRouteMatcher($location.path(), route))) { |
||||
match = inherit(route, { |
||||
params: angular.extend({}, $location.search(), params), |
||||
pathParams: params}); |
||||
match.$$route = route; |
||||
} |
||||
}); |
||||
// No route matched; fallback to "otherwise" route
|
||||
return match || routes[null] && inherit(routes[null], {params: {}, pathParams:{}}); |
||||
} |
||||
|
||||
/** |
||||
* @returns interpolation of the redirect path with the parameters |
||||
*/ |
||||
function interpolate(string, params) { |
||||
var result = []; |
||||
angular.forEach((string||'').split(':'), function(segment, i) { |
||||
if (i === 0) { |
||||
result.push(segment); |
||||
} else { |
||||
var segmentMatch = segment.match(/(\w+)(.*)/); |
||||
var key = segmentMatch[1]; |
||||
result.push(params[key]); |
||||
result.push(segmentMatch[2] || ''); |
||||
delete params[key]; |
||||
} |
||||
}); |
||||
return result.join(''); |
||||
} |
||||
}]; |
||||
} |
||||
|
||||
ngRouteModule.provider('$routeParams', $RouteParamsProvider); |
||||
|
||||
|
||||
/** |
||||
* @ngdoc object |
||||
* @name ngRoute.$routeParams |
||||
* @requires $route |
||||
* |
||||
* @description |
||||
* The `$routeParams` service allows you to retrieve the current set of route parameters. |
||||
* |
||||
* Requires the {@link ngRoute `ngRoute`} module to be installed. |
||||
* |
||||
* The route parameters are a combination of {@link ng.$location `$location`}'s |
||||
* {@link ng.$location#methods_search `search()`} and {@link ng.$location#methods_path `path()`}. |
||||
* The `path` parameters are extracted when the {@link ngRoute.$route `$route`} path is matched. |
||||
* |
||||
* In case of parameter name collision, `path` params take precedence over `search` params. |
||||
* |
||||
* The service guarantees that the identity of the `$routeParams` object will remain unchanged |
||||
* (but its properties will likely change) even when a route change occurs. |
||||
* |
||||
* Note that the `$routeParams` are only updated *after* a route change completes successfully. |
||||
* This means that you cannot rely on `$routeParams` being correct in route resolve functions. |
||||
* Instead you can use `$route.current.params` to access the new route's parameters. |
||||
* |
||||
* @example |
||||
* <pre> |
||||
* // Given:
|
||||
* // URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
|
||||
* // Route: /Chapter/:chapterId/Section/:sectionId
|
||||
* //
|
||||
* // Then
|
||||
* $routeParams ==> {chapterId:1, sectionId:2, search:'moby'} |
||||
* </pre> |
||||
*/ |
||||
function $RouteParamsProvider() { |
||||
this.$get = function() { return {}; }; |
||||
} |
||||
|
||||
ngRouteModule.directive('ngView', ngViewFactory); |
||||
|
||||
/** |
||||
* @ngdoc directive |
||||
* @name ngRoute.directive:ngView |
||||
* @restrict ECA |
||||
* |
||||
* @description |
||||
* # Overview |
||||
* `ngView` is a directive that complements the {@link ngRoute.$route $route} service by |
||||
* including the rendered template of the current route into the main layout (`index.html`) file. |
||||
* Every time the current route changes, the included view changes with it according to the |
||||
* configuration of the `$route` service. |
||||
* |
||||
* Requires the {@link ngRoute `ngRoute`} module to be installed. |
||||
* |
||||
* @animations |
||||
* enter - animation is used to bring new content into the browser. |
||||
* leave - animation is used to animate existing content away. |
||||
* |
||||
* The enter and leave animation occur concurrently. |
||||
* |
||||
* @scope |
||||
* @priority 400 |
||||
* @example |
||||
<example module="ngViewExample" deps="angular-route.js" animations="true"> |
||||
<file name="index.html"> |
||||
<div ng-controller="MainCntl as main"> |
||||
Choose: |
||||
<a href="Book/Moby">Moby</a> | |
||||
<a href="Book/Moby/ch/1">Moby: Ch1</a> | |
||||
<a href="Book/Gatsby">Gatsby</a> | |
||||
<a href="Book/Gatsby/ch/4?key=value">Gatsby: Ch4</a> | |
||||
<a href="Book/Scarlet">Scarlet Letter</a><br/> |
||||
|
||||
<div class="view-animate-container"> |
||||
<div ng-view class="view-animate"></div> |
||||
</div> |
||||
<hr /> |
||||
|
||||
<pre>$location.path() = {{main.$location.path()}}</pre> |
||||
<pre>$route.current.templateUrl = {{main.$route.current.templateUrl}}</pre> |
||||
<pre>$route.current.params = {{main.$route.current.params}}</pre> |
||||
<pre>$route.current.scope.name = {{main.$route.current.scope.name}}</pre> |
||||
<pre>$routeParams = {{main.$routeParams}}</pre> |
||||
</div> |
||||
</file> |
||||
|
||||
<file name="book.html"> |
||||
<div> |
||||
controller: {{book.name}}<br /> |
||||
Book Id: {{book.params.bookId}}<br /> |
||||
</div> |
||||
</file> |
||||
|
||||
<file name="chapter.html"> |
||||
<div> |
||||
controller: {{chapter.name}}<br /> |
||||
Book Id: {{chapter.params.bookId}}<br /> |
||||
Chapter Id: {{chapter.params.chapterId}} |
||||
</div> |
||||
</file> |
||||
|
||||
<file name="animations.css"> |
||||
.view-animate-container { |
||||
position:relative; |
||||
height:100px!important; |
||||
position:relative; |
||||
background:white; |
||||
border:1px solid black; |
||||
height:40px; |
||||
overflow:hidden; |
||||
} |
||||
|
||||
.view-animate { |
||||
padding:10px; |
||||
} |
||||
|
||||
.view-animate.ng-enter, .view-animate.ng-leave { |
||||
-webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; |
||||
transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 1.5s; |
||||
|
||||
display:block; |
||||
width:100%; |
||||
border-left:1px solid black; |
||||
|
||||
position:absolute; |
||||
top:0; |
||||
left:0; |
||||
right:0; |
||||
bottom:0; |
||||
padding:10px; |
||||
} |
||||
|
||||
.view-animate.ng-enter { |
||||
left:100%; |
||||
} |
||||
.view-animate.ng-enter.ng-enter-active { |
||||
left:0; |
||||
} |
||||
.view-animate.ng-leave.ng-leave-active { |
||||
left:-100%; |
||||
} |
||||
</file> |
||||
|
||||
<file name="script.js"> |
||||
angular.module('ngViewExample', ['ngRoute', 'ngAnimate'], |
||||
function($routeProvider, $locationProvider) { |
||||
$routeProvider.when('/Book/:bookId', { |
||||
templateUrl: 'book.html', |
||||
controller: BookCntl, |
||||
controllerAs: 'book' |
||||
}); |
||||
$routeProvider.when('/Book/:bookId/ch/:chapterId', { |
||||
templateUrl: 'chapter.html', |
||||
controller: ChapterCntl, |
||||
controllerAs: 'chapter' |
||||
}); |
||||
|
||||
// configure html5 to get links working on jsfiddle
|
||||
$locationProvider.html5Mode(true); |
||||
}); |
||||
|
||||
function MainCntl($route, $routeParams, $location) { |
||||
this.$route = $route; |
||||
this.$location = $location; |
||||
this.$routeParams = $routeParams; |
||||
} |
||||
|
||||
function BookCntl($routeParams) { |
||||
this.name = "BookCntl"; |
||||
this.params = $routeParams; |
||||
} |
||||
|
||||
function ChapterCntl($routeParams) { |
||||
this.name = "ChapterCntl"; |
||||
this.params = $routeParams; |
||||
} |
||||
</file> |
||||
|
||||
<file name="scenario.js"> |
||||
it('should load and compile correct template', function() { |
||||
element('a:contains("Moby: Ch1")').click(); |
||||
var content = element('.doc-example-live [ng-view]').text(); |
||||
expect(content).toMatch(/controller\: ChapterCntl/); |
||||
expect(content).toMatch(/Book Id\: Moby/); |
||||
expect(content).toMatch(/Chapter Id\: 1/); |
||||
|
||||
element('a:contains("Scarlet")').click(); |
||||
content = element('.doc-example-live [ng-view]').text(); |
||||
expect(content).toMatch(/controller\: BookCntl/); |
||||
expect(content).toMatch(/Book Id\: Scarlet/); |
||||
}); |
||||
</file> |
||||
</example> |
||||
*/ |
||||
|
||||
|
||||
/** |
||||
* @ngdoc event |
||||
* @name ngRoute.directive:ngView#$viewContentLoaded |
||||
* @eventOf ngRoute.directive:ngView |
||||
* @eventType emit on the current ngView scope |
||||
* @description |
||||
* Emitted every time the ngView content is reloaded. |
||||
*/ |
||||
ngViewFactory.$inject = ['$route', '$anchorScroll', '$compile', '$controller', '$animate']; |
||||
function ngViewFactory( $route, $anchorScroll, $compile, $controller, $animate) { |
||||
return { |
||||
restrict: 'ECA', |
||||
terminal: true, |
||||
priority: 400, |
||||
transclude: 'element', |
||||
link: function(scope, $element, attr, ctrl, $transclude) { |
||||
var currentScope, |
||||
currentElement, |
||||
autoScrollExp = attr.autoscroll, |
||||
onloadExp = attr.onload || ''; |
||||
|
||||
scope.$on('$routeChangeSuccess', update); |
||||
update(); |
||||
|
||||
function cleanupLastView() { |
||||
if (currentScope) { |
||||
currentScope.$destroy(); |
||||
currentScope = null; |
||||
} |
||||
if(currentElement) { |
||||
$animate.leave(currentElement); |
||||
currentElement = null; |
||||
} |
||||
} |
||||
|
||||
function update() { |
||||
var locals = $route.current && $route.current.locals, |
||||
template = locals && locals.$template; |
||||
|
||||
if (template) { |
||||
var newScope = scope.$new(); |
||||
|
||||
// Note: This will also link all children of ng-view that were contained in the original
|
||||
// html. If that content contains controllers, ... they could pollute/change the scope.
|
||||
// However, using ng-view on an element with additional content does not make sense...
|
||||
// Note: We can't remove them in the cloneAttchFn of $transclude as that
|
||||
// function is called before linking the content, which would apply child
|
||||
// directives to non existing elements.
|
||||
var clone = $transclude(newScope, angular.noop); |
||||
clone.html(template); |
||||
$animate.enter(clone, null, currentElement || $element, function onNgViewEnter () { |
||||
if (angular.isDefined(autoScrollExp) |
||||
&& (!autoScrollExp || scope.$eval(autoScrollExp))) { |
||||
$anchorScroll(); |
||||
} |
||||
}); |
||||
|
||||
cleanupLastView(); |
||||
|
||||
var link = $compile(clone.contents()), |
||||
current = $route.current; |
||||
|
||||
currentScope = current.scope = newScope; |
||||
currentElement = clone; |
||||
|
||||
if (current.controller) { |
||||
locals.$scope = currentScope; |
||||
var controller = $controller(current.controller, locals); |
||||
if (current.controllerAs) { |
||||
currentScope[current.controllerAs] = controller; |
||||
} |
||||
clone.data('$ngControllerController', controller); |
||||
clone.children().data('$ngControllerController', controller); |
||||
} |
||||
|
||||
link(currentScope); |
||||
currentScope.$emit('$viewContentLoaded'); |
||||
currentScope.$eval(onloadExp); |
||||
} else { |
||||
cleanupLastView(); |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
} |
||||
|
||||
|
||||
})(window, window.angular); |
@ -0,0 +1,14 @@
|
||||
/* |
||||
AngularJS v1.2.3 |
||||
(c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
License: MIT |
||||
*/ |
||||
(function(t,c,A){'use strict';function x(r,m,d,b,h){return{restrict:"ECA",terminal:!0,priority:400,transclude:"element",link:function(l,z,k,B,w){function v(){g&&(g.$destroy(),g=null);q&&(h.leave(q),q=null)}function u(){var a=r.current&&r.current.locals,e=a&&a.$template;if(e){var y=l.$new(),s=w(y,c.noop);s.html(e);h.enter(s,null,q||z,function(){!c.isDefined(n)||n&&!l.$eval(n)||m()});v();var e=d(s.contents()),f=r.current;g=f.scope=y;q=s;f.controller&&(a.$scope=g,a=b(f.controller,a),f.controllerAs&& |
||||
(g[f.controllerAs]=a),s.data("$ngControllerController",a),s.children().data("$ngControllerController",a));e(g);g.$emit("$viewContentLoaded");g.$eval(p)}else v()}var g,q,n=k.autoscroll,p=k.onload||"";l.$on("$routeChangeSuccess",u);u()}}}t=c.module("ngRoute",["ng"]).provider("$route",function(){function r(b,h){return c.extend(new (c.extend(function(){},{prototype:b})),h)}function m(b,c){var l=c.caseInsensitiveMatch,d={originalPath:b,regexp:b},k=d.keys=[];b=b.replace(/([().])/g,"\\$1").replace(/(\/)?:(\w+)([\?|\*])?/g, |
||||
function(b,c,h,d){b="?"===d?d:null;d="*"===d?d:null;k.push({name:h,optional:!!b});c=c||"";return""+(b?"":c)+"(?:"+(b?c:"")+(d&&"(.+?)"||"([^/]+)")+(b||"")+")"+(b||"")}).replace(/([\/$\*])/g,"\\$1");d.regexp=RegExp("^"+b+"$",l?"i":"");return d}var d={};this.when=function(b,h){d[b]=c.extend({reloadOnSearch:!0},h,b&&m(b,h));if(b){var l="/"==b[b.length-1]?b.substr(0,b.length-1):b+"/";d[l]=c.extend({redirectTo:b},m(l,h))}return this};this.otherwise=function(b){this.when(null,b);return this};this.$get= |
||||
["$rootScope","$location","$routeParams","$q","$injector","$http","$templateCache","$sce",function(b,h,l,m,k,t,w,v){function u(){var a=g(),e=p.current;if(a&&e&&a.$$route===e.$$route&&c.equals(a.pathParams,e.pathParams)&&!a.reloadOnSearch&&!n)e.params=a.params,c.copy(e.params,l),b.$broadcast("$routeUpdate",e);else if(a||e)n=!1,b.$broadcast("$routeChangeStart",a,e),(p.current=a)&&a.redirectTo&&(c.isString(a.redirectTo)?h.path(q(a.redirectTo,a.params)).search(a.params).replace():h.url(a.redirectTo(a.pathParams, |
||||
h.path(),h.search())).replace()),m.when(a).then(function(){if(a){var b=c.extend({},a.resolve),e,f;c.forEach(b,function(a,e){b[e]=c.isString(a)?k.get(a):k.invoke(a)});c.isDefined(e=a.template)?c.isFunction(e)&&(e=e(a.params)):c.isDefined(f=a.templateUrl)&&(c.isFunction(f)&&(f=f(a.params)),f=v.getTrustedResourceUrl(f),c.isDefined(f)&&(a.loadedTemplateUrl=f,e=t.get(f,{cache:w}).then(function(a){return a.data})));c.isDefined(e)&&(b.$template=e);return m.all(b)}}).then(function(d){a==p.current&&(a&&(a.locals= |
||||
d,c.copy(a.params,l)),b.$broadcast("$routeChangeSuccess",a,e))},function(c){a==p.current&&b.$broadcast("$routeChangeError",a,e,c)})}function g(){var a,b;c.forEach(d,function(d,l){var f;if(f=!b){var g=h.path();f=d.keys;var m={};if(d.regexp)if(g=d.regexp.exec(g)){for(var k=1,q=g.length;k<q;++k){var n=f[k-1],p="string"==typeof g[k]?decodeURIComponent(g[k]):g[k];n&&p&&(m[n.name]=p)}f=m}else f=null;else f=null;f=a=f}f&&(b=r(d,{params:c.extend({},h.search(),a),pathParams:a}),b.$$route=d)});return b||d[null]&& |
||||
r(d[null],{params:{},pathParams:{}})}function q(a,b){var d=[];c.forEach((a||"").split(":"),function(a,c){if(0===c)d.push(a);else{var g=a.match(/(\w+)(.*)/),h=g[1];d.push(b[h]);d.push(g[2]||"");delete b[h]}});return d.join("")}var n=!1,p={routes:d,reload:function(){n=!0;b.$evalAsync(u)}};b.$on("$locationChangeSuccess",u);return p}]});t.provider("$routeParams",function(){this.$get=function(){return{}}});t.directive("ngView",x);x.$inject=["$route","$anchorScroll","$compile","$controller","$animate"]})(window, |
||||
window.angular); |
||||
//# sourceMappingURL=angular-route.min.js.map
|
@ -0,0 +1,615 @@
|
||||
/** |
||||
* @license AngularJS v1.2.3 |
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* License: MIT |
||||
*/ |
||||
(function(window, angular, undefined) {'use strict'; |
||||
|
||||
var $sanitizeMinErr = angular.$$minErr('$sanitize'); |
||||
|
||||
/** |
||||
* @ngdoc overview |
||||
* @name ngSanitize |
||||
* @description |
||||
* |
||||
* # ngSanitize |
||||
* |
||||
* The `ngSanitize` module provides functionality to sanitize HTML. |
||||
* |
||||
* {@installModule sanitize} |
||||
* |
||||
* <div doc-module-components="ngSanitize"></div> |
||||
* |
||||
* See {@link ngSanitize.$sanitize `$sanitize`} for usage. |
||||
*/ |
||||
|
||||
/* |
||||
* HTML Parser By Misko Hevery (misko@hevery.com) |
||||
* based on: HTML Parser By John Resig (ejohn.org) |
||||
* Original code by Erik Arvidsson, Mozilla Public License |
||||
* http://erik.eae.net/simplehtmlparser/simplehtmlparser.js
|
||||
* |
||||
* // Use like so:
|
||||
* htmlParser(htmlString, { |
||||
* start: function(tag, attrs, unary) {}, |
||||
* end: function(tag) {}, |
||||
* chars: function(text) {}, |
||||
* comment: function(text) {} |
||||
* }); |
||||
* |
||||
*/ |
||||
|
||||
|
||||
/** |
||||
* @ngdoc service |
||||
* @name ngSanitize.$sanitize |
||||
* @function |
||||
* |
||||
* @description |
||||
* The input is sanitized by parsing the html into tokens. All safe tokens (from a whitelist) are |
||||
* then serialized back to properly escaped html string. This means that no unsafe input can make |
||||
* it into the returned string, however, since our parser is more strict than a typical browser |
||||
* parser, it's possible that some obscure input, which would be recognized as valid HTML by a |
||||
* browser, won't make it through the sanitizer. |
||||
* The whitelist is configured using the functions `aHrefSanitizationWhitelist` and |
||||
* `imgSrcSanitizationWhitelist` of {@link ng.$compileProvider `$compileProvider`}. |
||||
* |
||||
* @param {string} html Html input. |
||||
* @returns {string} Sanitized html. |
||||
* |
||||
* @example |
||||
<doc:example module="ngSanitize"> |
||||
<doc:source> |
||||
<script> |
||||
function Ctrl($scope, $sce) { |
||||
$scope.snippet = |
||||
'<p style="color:blue">an html\n' + |
||||
'<em onmouseover="this.textContent=\'PWN3D!\'">click here</em>\n' + |
||||
'snippet</p>'; |
||||
$scope.deliberatelyTrustDangerousSnippet = function() { |
||||
return $sce.trustAsHtml($scope.snippet); |
||||
}; |
||||
} |
||||
</script> |
||||
<div ng-controller="Ctrl"> |
||||
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea> |
||||
<table> |
||||
<tr> |
||||
<td>Directive</td> |
||||
<td>How</td> |
||||
<td>Source</td> |
||||
<td>Rendered</td> |
||||
</tr> |
||||
<tr id="bind-html-with-sanitize"> |
||||
<td>ng-bind-html</td> |
||||
<td>Automatically uses $sanitize</td> |
||||
<td><pre><div ng-bind-html="snippet"><br/></div></pre></td> |
||||
<td><div ng-bind-html="snippet"></div></td> |
||||
</tr> |
||||
<tr id="bind-html-with-trust"> |
||||
<td>ng-bind-html</td> |
||||
<td>Bypass $sanitize by explicitly trusting the dangerous value</td> |
||||
<td> |
||||
<pre><div ng-bind-html="deliberatelyTrustDangerousSnippet()"> |
||||
</div></pre> |
||||
</td> |
||||
<td><div ng-bind-html="deliberatelyTrustDangerousSnippet()"></div></td> |
||||
</tr> |
||||
<tr id="bind-default"> |
||||
<td>ng-bind</td> |
||||
<td>Automatically escapes</td> |
||||
<td><pre><div ng-bind="snippet"><br/></div></pre></td> |
||||
<td><div ng-bind="snippet"></div></td> |
||||
</tr> |
||||
</table> |
||||
</div> |
||||
</doc:source> |
||||
<doc:scenario> |
||||
it('should sanitize the html snippet by default', function() { |
||||
expect(using('#bind-html-with-sanitize').element('div').html()). |
||||
toBe('<p>an html\n<em>click here</em>\nsnippet</p>'); |
||||
}); |
||||
|
||||
it('should inline raw snippet if bound to a trusted value', function() { |
||||
expect(using('#bind-html-with-trust').element("div").html()). |
||||
toBe("<p style=\"color:blue\">an html\n" + |
||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + |
||||
"snippet</p>"); |
||||
}); |
||||
|
||||
it('should escape snippet without any filter', function() { |
||||
expect(using('#bind-default').element('div').html()). |
||||
toBe("<p style=\"color:blue\">an html\n" + |
||||
"<em onmouseover=\"this.textContent='PWN3D!'\">click here</em>\n" + |
||||
"snippet</p>"); |
||||
}); |
||||
|
||||
it('should update', function() { |
||||
input('snippet').enter('new <b onclick="alert(1)">text</b>'); |
||||
expect(using('#bind-html-with-sanitize').element('div').html()).toBe('new <b>text</b>'); |
||||
expect(using('#bind-html-with-trust').element('div').html()).toBe( |
||||
'new <b onclick="alert(1)">text</b>'); |
||||
expect(using('#bind-default').element('div').html()).toBe( |
||||
"new <b onclick=\"alert(1)\">text</b>"); |
||||
}); |
||||
</doc:scenario> |
||||
</doc:example> |
||||
*/ |
||||
function $SanitizeProvider() { |
||||
this.$get = ['$$sanitizeUri', function($$sanitizeUri) { |
||||
return function(html) { |
||||
var buf = []; |
||||
htmlParser(html, htmlSanitizeWriter(buf, function(uri, isImage) { |
||||
return !/^unsafe/.test($$sanitizeUri(uri, isImage)); |
||||
})); |
||||
return buf.join(''); |
||||
}; |
||||
}]; |
||||
} |
||||
|
||||
function sanitizeText(chars) { |
||||
var buf = []; |
||||
var writer = htmlSanitizeWriter(buf, angular.noop); |
||||
writer.chars(chars); |
||||
return buf.join(''); |
||||
} |
||||
|
||||
|
||||
// Regular Expressions for parsing tags and attributes
|
||||
var START_TAG_REGEXP = |
||||
/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/, |
||||
END_TAG_REGEXP = /^<\s*\/\s*([\w:-]+)[^>]*>/, |
||||
ATTR_REGEXP = /([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g, |
||||
BEGIN_TAG_REGEXP = /^</, |
||||
BEGING_END_TAGE_REGEXP = /^<\s*\//, |
||||
COMMENT_REGEXP = /<!--(.*?)-->/g, |
||||
DOCTYPE_REGEXP = /<!DOCTYPE([^>]*?)>/i, |
||||
CDATA_REGEXP = /<!\[CDATA\[(.*?)]]>/g, |
||||
// Match everything outside of normal chars and " (quote character)
|
||||
NON_ALPHANUMERIC_REGEXP = /([^\#-~| |!])/g; |
||||
|
||||
|
||||
// Good source of info about elements and attributes
|
||||
// http://dev.w3.org/html5/spec/Overview.html#semantics
|
||||
// http://simon.html5.org/html-elements
|
||||
|
||||
// Safe Void Elements - HTML5
|
||||
// http://dev.w3.org/html5/spec/Overview.html#void-elements
|
||||
var voidElements = makeMap("area,br,col,hr,img,wbr"); |
||||
|
||||
// Elements that you can, intentionally, leave open (and which close themselves)
|
||||
// http://dev.w3.org/html5/spec/Overview.html#optional-tags
|
||||
var optionalEndTagBlockElements = makeMap("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr"), |
||||
optionalEndTagInlineElements = makeMap("rp,rt"), |
||||
optionalEndTagElements = angular.extend({}, |
||||
optionalEndTagInlineElements, |
||||
optionalEndTagBlockElements); |
||||
|
||||
// Safe Block Elements - HTML5
|
||||
var blockElements = angular.extend({}, optionalEndTagBlockElements, makeMap("address,article," + |
||||
"aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5," + |
||||
"h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")); |
||||
|
||||
// Inline Elements - HTML5
|
||||
var inlineElements = angular.extend({}, optionalEndTagInlineElements, makeMap("a,abbr,acronym,b," + |
||||
"bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s," + |
||||
"samp,small,span,strike,strong,sub,sup,time,tt,u,var")); |
||||
|
||||
|
||||
// Special Elements (can contain anything)
|
||||
var specialElements = makeMap("script,style"); |
||||
|
||||
var validElements = angular.extend({}, |
||||
voidElements, |
||||
blockElements, |
||||
inlineElements, |
||||
optionalEndTagElements); |
||||
|
||||
//Attributes that have href and hence need to be sanitized
|
||||
var uriAttrs = makeMap("background,cite,href,longdesc,src,usemap"); |
||||
var validAttrs = angular.extend({}, uriAttrs, makeMap( |
||||
'abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,'+ |
||||
'color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,'+ |
||||
'ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,'+ |
||||
'scope,scrolling,shape,span,start,summary,target,title,type,'+ |
||||
'valign,value,vspace,width')); |
||||
|
||||
function makeMap(str) { |
||||
var obj = {}, items = str.split(','), i; |
||||
for (i = 0; i < items.length; i++) obj[items[i]] = true; |
||||
return obj; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* @example |
||||
* htmlParser(htmlString, { |
||||
* start: function(tag, attrs, unary) {}, |
||||
* end: function(tag) {}, |
||||
* chars: function(text) {}, |
||||
* comment: function(text) {} |
||||
* }); |
||||
* |
||||
* @param {string} html string |
||||
* @param {object} handler |
||||
*/ |
||||
function htmlParser( html, handler ) { |
||||
var index, chars, match, stack = [], last = html; |
||||
stack.last = function() { return stack[ stack.length - 1 ]; }; |
||||
|
||||
while ( html ) { |
||||
chars = true; |
||||
|
||||
// Make sure we're not in a script or style element
|
||||
if ( !stack.last() || !specialElements[ stack.last() ] ) { |
||||
|
||||
// Comment
|
||||
if ( html.indexOf("<!--") === 0 ) { |
||||
// comments containing -- are not allowed unless they terminate the comment
|
||||
index = html.indexOf("--", 4); |
||||
|
||||
if ( index >= 0 && html.lastIndexOf("-->", index) === index) { |
||||
if (handler.comment) handler.comment( html.substring( 4, index ) ); |
||||
html = html.substring( index + 3 ); |
||||
chars = false; |
||||
} |
||||
// DOCTYPE
|
||||
} else if ( DOCTYPE_REGEXP.test(html) ) { |
||||
match = html.match( DOCTYPE_REGEXP ); |
||||
|
||||
if ( match ) { |
||||
html = html.replace( match[0] , ''); |
||||
chars = false; |
||||
} |
||||
// end tag
|
||||
} else if ( BEGING_END_TAGE_REGEXP.test(html) ) { |
||||
match = html.match( END_TAG_REGEXP ); |
||||
|
||||
if ( match ) { |
||||
html = html.substring( match[0].length ); |
||||
match[0].replace( END_TAG_REGEXP, parseEndTag ); |
||||
chars = false; |
||||
} |
||||
|
||||
// start tag
|
||||
} else if ( BEGIN_TAG_REGEXP.test(html) ) { |
||||
match = html.match( START_TAG_REGEXP ); |
||||
|
||||
if ( match ) { |
||||
html = html.substring( match[0].length ); |
||||
match[0].replace( START_TAG_REGEXP, parseStartTag ); |
||||
chars = false; |
||||
} |
||||
} |
||||
|
||||
if ( chars ) { |
||||
index = html.indexOf("<"); |
||||
|
||||
var text = index < 0 ? html : html.substring( 0, index ); |
||||
html = index < 0 ? "" : html.substring( index ); |
||||
|
||||
if (handler.chars) handler.chars( decodeEntities(text) ); |
||||
} |
||||
|
||||
} else { |
||||
html = html.replace(new RegExp("(.*)<\\s*\\/\\s*" + stack.last() + "[^>]*>", 'i'), |
||||
function(all, text){ |
||||
text = text.replace(COMMENT_REGEXP, "$1").replace(CDATA_REGEXP, "$1"); |
||||
|
||||
if (handler.chars) handler.chars( decodeEntities(text) ); |
||||
|
||||
return ""; |
||||
}); |
||||
|
||||
parseEndTag( "", stack.last() ); |
||||
} |
||||
|
||||
if ( html == last ) { |
||||
throw $sanitizeMinErr('badparse', "The sanitizer was unable to parse the following block " + |
||||
"of html: {0}", html); |
||||
} |
||||
last = html; |
||||
} |
||||
|
||||
// Clean up any remaining tags
|
||||
parseEndTag(); |
||||
|
||||
function parseStartTag( tag, tagName, rest, unary ) { |
||||
tagName = angular.lowercase(tagName); |
||||
if ( blockElements[ tagName ] ) { |
||||
while ( stack.last() && inlineElements[ stack.last() ] ) { |
||||
parseEndTag( "", stack.last() ); |
||||
} |
||||
} |
||||
|
||||
if ( optionalEndTagElements[ tagName ] && stack.last() == tagName ) { |
||||
parseEndTag( "", tagName ); |
||||
} |
||||
|
||||
unary = voidElements[ tagName ] || !!unary; |
||||
|
||||
if ( !unary ) |
||||
stack.push( tagName ); |
||||
|
||||
var attrs = {}; |
||||
|
||||
rest.replace(ATTR_REGEXP, |
||||
function(match, name, doubleQuotedValue, singleQuotedValue, unquotedValue) { |
||||
var value = doubleQuotedValue |
||||
|| singleQuotedValue |
||||
|| unquotedValue |
||||
|| ''; |
||||
|
||||
attrs[name] = decodeEntities(value); |
||||
}); |
||||
if (handler.start) handler.start( tagName, attrs, unary ); |
||||
} |
||||
|
||||
function parseEndTag( tag, tagName ) { |
||||
var pos = 0, i; |
||||
tagName = angular.lowercase(tagName); |
||||
if ( tagName ) |
||||
// Find the closest opened tag of the same type
|
||||
for ( pos = stack.length - 1; pos >= 0; pos-- ) |
||||
if ( stack[ pos ] == tagName ) |
||||
break; |
||||
|
||||
if ( pos >= 0 ) { |
||||
// Close all the open elements, up the stack
|
||||
for ( i = stack.length - 1; i >= pos; i-- ) |
||||
if (handler.end) handler.end( stack[ i ] ); |
||||
|
||||
// Remove the open elements from the stack
|
||||
stack.length = pos; |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* decodes all entities into regular string |
||||
* @param value |
||||
* @returns {string} A string with decoded entities. |
||||
*/ |
||||
var hiddenPre=document.createElement("pre"); |
||||
function decodeEntities(value) { |
||||
if (!value) { |
||||
return ''; |
||||
} |
||||
// Note: IE8 does not preserve spaces at the start/end of innerHTML
|
||||
var spaceRe = /^(\s*)([\s\S]*?)(\s*)$/; |
||||
var parts = spaceRe.exec(value); |
||||
parts[0] = ''; |
||||
if (parts[2]) { |
||||
hiddenPre.innerHTML=parts[2].replace(/</g,"<"); |
||||
parts[2] = hiddenPre.innerText || hiddenPre.textContent; |
||||
} |
||||
return parts.join(''); |
||||
} |
||||
|
||||
/** |
||||
* Escapes all potentially dangerous characters, so that the |
||||
* resulting string can be safely inserted into attribute or |
||||
* element text. |
||||
* @param value |
||||
* @returns escaped text |
||||
*/ |
||||
function encodeEntities(value) { |
||||
return value. |
||||
replace(/&/g, '&'). |
||||
replace(NON_ALPHANUMERIC_REGEXP, function(value){ |
||||
return '&#' + value.charCodeAt(0) + ';'; |
||||
}). |
||||
replace(/</g, '<'). |
||||
replace(/>/g, '>'); |
||||
} |
||||
|
||||
/** |
||||
* create an HTML/XML writer which writes to buffer |
||||
* @param {Array} buf use buf.jain('') to get out sanitized html string |
||||
* @returns {object} in the form of { |
||||
* start: function(tag, attrs, unary) {}, |
||||
* end: function(tag) {}, |
||||
* chars: function(text) {}, |
||||
* comment: function(text) {} |
||||
* } |
||||
*/ |
||||
function htmlSanitizeWriter(buf, uriValidator){ |
||||
var ignore = false; |
||||
var out = angular.bind(buf, buf.push); |
||||
return { |
||||
start: function(tag, attrs, unary){ |
||||
tag = angular.lowercase(tag); |
||||
if (!ignore && specialElements[tag]) { |
||||
ignore = tag; |
||||
} |
||||
if (!ignore && validElements[tag] === true) { |
||||
out('<'); |
||||
out(tag); |
||||
angular.forEach(attrs, function(value, key){ |
||||
var lkey=angular.lowercase(key); |
||||
var isImage = (tag === 'img' && lkey === 'src') || (lkey === 'background'); |
||||
if (validAttrs[lkey] === true && |
||||
(uriAttrs[lkey] !== true || uriValidator(value, isImage))) { |
||||
out(' '); |
||||
out(key); |
||||
out('="'); |
||||
out(encodeEntities(value)); |
||||
out('"'); |
||||
} |
||||
}); |
||||
out(unary ? '/>' : '>'); |
||||
} |
||||
}, |
||||
end: function(tag){ |
||||
tag = angular.lowercase(tag); |
||||
if (!ignore && validElements[tag] === true) { |
||||
out('</'); |
||||
out(tag); |
||||
out('>'); |
||||
} |
||||
if (tag == ignore) { |
||||
ignore = false; |
||||
} |
||||
}, |
||||
chars: function(chars){ |
||||
if (!ignore) { |
||||
out(encodeEntities(chars)); |
||||
} |
||||
} |
||||
}; |
||||
} |
||||
|
||||
|
||||
// define ngSanitize module and register $sanitize service
|
||||
angular.module('ngSanitize', []).provider('$sanitize', $SanitizeProvider); |
||||
|
||||
/* global sanitizeText: false */ |
||||
|
||||
/** |
||||
* @ngdoc filter |
||||
* @name ngSanitize.filter:linky |
||||
* @function |
||||
* |
||||
* @description |
||||
* Finds links in text input and turns them into html links. Supports http/https/ftp/mailto and |
||||
* plain email address links. |
||||
* |
||||
* Requires the {@link ngSanitize `ngSanitize`} module to be installed. |
||||
* |
||||
* @param {string} text Input text. |
||||
* @param {string} target Window (_blank|_self|_parent|_top) or named frame to open links in. |
||||
* @returns {string} Html-linkified text. |
||||
* |
||||
* @usage |
||||
<span ng-bind-html="linky_expression | linky"></span> |
||||
* |
||||
* @example |
||||
<doc:example module="ngSanitize"> |
||||
<doc:source> |
||||
<script> |
||||
function Ctrl($scope) { |
||||
$scope.snippet = |
||||
'Pretty text with some links:\n'+ |
||||
'http://angularjs.org/,\n'+ |
||||
'mailto:us@somewhere.org,\n'+ |
||||
'another@somewhere.org,\n'+ |
||||
'and one more: ftp://127.0.0.1/.'; |
||||
$scope.snippetWithTarget = 'http://angularjs.org/'; |
||||
} |
||||
</script> |
||||
<div ng-controller="Ctrl"> |
||||
Snippet: <textarea ng-model="snippet" cols="60" rows="3"></textarea> |
||||
<table> |
||||
<tr> |
||||
<td>Filter</td> |
||||
<td>Source</td> |
||||
<td>Rendered</td> |
||||
</tr> |
||||
<tr id="linky-filter"> |
||||
<td>linky filter</td> |
||||
<td> |
||||
<pre><div ng-bind-html="snippet | linky"><br></div></pre> |
||||
</td> |
||||
<td> |
||||
<div ng-bind-html="snippet | linky"></div> |
||||
</td> |
||||
</tr> |
||||
<tr id="linky-target"> |
||||
<td>linky target</td> |
||||
<td> |
||||
<pre><div ng-bind-html="snippetWithTarget | linky:'_blank'"><br></div></pre> |
||||
</td> |
||||
<td> |
||||
<div ng-bind-html="snippetWithTarget | linky:'_blank'"></div> |
||||
</td> |
||||
</tr> |
||||
<tr id="escaped-html"> |
||||
<td>no filter</td> |
||||
<td><pre><div ng-bind="snippet"><br></div></pre></td> |
||||
<td><div ng-bind="snippet"></div></td> |
||||
</tr> |
||||
</table> |
||||
</doc:source> |
||||
<doc:scenario> |
||||
it('should linkify the snippet with urls', function() { |
||||
expect(using('#linky-filter').binding('snippet | linky')). |
||||
toBe('Pretty text with some links: ' + |
||||
'<a href="http://angularjs.org/">http://angularjs.org/</a>, ' + |
||||
'<a href="mailto:us@somewhere.org">us@somewhere.org</a>, ' + |
||||
'<a href="mailto:another@somewhere.org">another@somewhere.org</a>, ' + |
||||
'and one more: <a href="ftp://127.0.0.1/">ftp://127.0.0.1/</a>.'); |
||||
}); |
||||
|
||||
it ('should not linkify snippet without the linky filter', function() { |
||||
expect(using('#escaped-html').binding('snippet')). |
||||
toBe("Pretty text with some links:\n" + |
||||
"http://angularjs.org/,\n" + |
||||
"mailto:us@somewhere.org,\n" + |
||||
"another@somewhere.org,\n" + |
||||
"and one more: ftp://127.0.0.1/."); |
||||
}); |
||||
|
||||
it('should update', function() { |
||||
input('snippet').enter('new http://link.'); |
||||
expect(using('#linky-filter').binding('snippet | linky')). |
||||
toBe('new <a href="http://link">http://link</a>.'); |
||||
expect(using('#escaped-html').binding('snippet')).toBe('new http://link.'); |
||||
}); |
||||
|
||||
it('should work with the target property', function() { |
||||
expect(using('#linky-target').binding("snippetWithTarget | linky:'_blank'")). |
||||
toBe('<a target="_blank" href="http://angularjs.org/">http://angularjs.org/</a>'); |
||||
}); |
||||
</doc:scenario> |
||||
</doc:example> |
||||
*/ |
||||
angular.module('ngSanitize').filter('linky', ['$sanitize', function($sanitize) { |
||||
var LINKY_URL_REGEXP = |
||||
/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/, |
||||
MAILTO_REGEXP = /^mailto:/; |
||||
|
||||
return function(text, target) { |
||||
if (!text) return text; |
||||
var match; |
||||
var raw = text; |
||||
var html = []; |
||||
var url; |
||||
var i; |
||||
while ((match = raw.match(LINKY_URL_REGEXP))) { |
||||
// We can not end in these as they are sometimes found at the end of the sentence
|
||||
url = match[0]; |
||||
// if we did not match ftp/http/mailto then assume mailto
|
||||
if (match[2] == match[3]) url = 'mailto:' + url; |
||||
i = match.index; |
||||
addText(raw.substr(0, i)); |
||||
addLink(url, match[0].replace(MAILTO_REGEXP, '')); |
||||
raw = raw.substring(i + match[0].length); |
||||
} |
||||
addText(raw); |
||||
return $sanitize(html.join('')); |
||||
|
||||
function addText(text) { |
||||
if (!text) { |
||||
return; |
||||
} |
||||
html.push(sanitizeText(text)); |
||||
} |
||||
|
||||
function addLink(url, text) { |
||||
html.push('<a '); |
||||
if (angular.isDefined(target)) { |
||||
html.push('target="'); |
||||
html.push(target); |
||||
html.push('" '); |
||||
} |
||||
html.push('href="'); |
||||
html.push(url); |
||||
html.push('">'); |
||||
addText(text); |
||||
html.push('</a>'); |
||||
} |
||||
}; |
||||
}]); |
||||
|
||||
|
||||
})(window, window.angular); |
@ -0,0 +1,14 @@
|
||||
/* |
||||
AngularJS v1.2.3 |
||||
(c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
License: MIT |
||||
*/ |
||||
(function(n,h,q){'use strict';function F(a){var e=[];t(e,h.noop).chars(a);return e.join("")}function k(a){var e={};a=a.split(",");var d;for(d=0;d<a.length;d++)e[a[d]]=!0;return e}function G(a,e){function d(a,b,d,g){b=h.lowercase(b);if(u[b])for(;f.last()&&v[f.last()];)c("",f.last());w[b]&&f.last()==b&&c("",b);(g=x[b]||!!g)||f.push(b);var l={};d.replace(H,function(a,b,e,c,m){l[b]=r(e||c||m||"")});e.start&&e.start(b,l,g)}function c(a,b){var c=0,d;if(b=h.lowercase(b))for(c=f.length-1;0<=c&&f[c]!=b;c--); |
||||
if(0<=c){for(d=f.length-1;d>=c;d--)e.end&&e.end(f[d]);f.length=c}}var b,g,f=[],l=a;for(f.last=function(){return f[f.length-1]};a;){g=!0;if(f.last()&&y[f.last()])a=a.replace(RegExp("(.*)<\\s*\\/\\s*"+f.last()+"[^>]*>","i"),function(a,b){b=b.replace(I,"$1").replace(J,"$1");e.chars&&e.chars(r(b));return""}),c("",f.last());else{if(0===a.indexOf("\x3c!--"))b=a.indexOf("--",4),0<=b&&a.lastIndexOf("--\x3e",b)===b&&(e.comment&&e.comment(a.substring(4,b)),a=a.substring(b+3),g=!1);else if(z.test(a)){if(b=a.match(z))a= |
||||
a.replace(b[0],""),g=!1}else if(K.test(a)){if(b=a.match(A))a=a.substring(b[0].length),b[0].replace(A,c),g=!1}else L.test(a)&&(b=a.match(B))&&(a=a.substring(b[0].length),b[0].replace(B,d),g=!1);g&&(b=a.indexOf("<"),g=0>b?a:a.substring(0,b),a=0>b?"":a.substring(b),e.chars&&e.chars(r(g)))}if(a==l)throw M("badparse",a);l=a}c()}function r(a){if(!a)return"";a=/^(\s*)([\s\S]*?)(\s*)$/.exec(a);a[0]="";a[2]&&(s.innerHTML=a[2].replace(/</g,"<"),a[2]=s.innerText||s.textContent);return a.join("")}function C(a){return a.replace(/&/g, |
||||
"&").replace(N,function(a){return"&#"+a.charCodeAt(0)+";"}).replace(/</g,"<").replace(/>/g,">")}function t(a,e){var d=!1,c=h.bind(a,a.push);return{start:function(a,g,f){a=h.lowercase(a);!d&&y[a]&&(d=a);d||!0!==D[a]||(c("<"),c(a),h.forEach(g,function(d,f){var g=h.lowercase(f),k="img"===a&&"src"===g||"background"===g;!0!==O[g]||!0===E[g]&&!e(d,k)||(c(" "),c(f),c('="'),c(C(d)),c('"'))}),c(f?"/>":">"))},end:function(a){a=h.lowercase(a);d||!0!==D[a]||(c("</"),c(a),c(">"));a==d&&(d=!1)},chars:function(a){d|| |
||||
c(C(a))}}}var M=h.$$minErr("$sanitize"),B=/^<\s*([\w:-]+)((?:\s+[\w:-]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/,A=/^<\s*\/\s*([\w:-]+)[^>]*>/,H=/([\w:-]+)(?:\s*=\s*(?:(?:"((?:[^"])*)")|(?:'((?:[^'])*)')|([^>\s]+)))?/g,L=/^</,K=/^<\s*\//,I=/\x3c!--(.*?)--\x3e/g,z=/<!DOCTYPE([^>]*?)>/i,J=/<!\[CDATA\[(.*?)]]\x3e/g,N=/([^\#-~| |!])/g,x=k("area,br,col,hr,img,wbr");n=k("colgroup,dd,dt,li,p,tbody,td,tfoot,th,thead,tr");q=k("rp,rt");var w=h.extend({},q,n),u=h.extend({},n,k("address,article,aside,blockquote,caption,center,del,dir,div,dl,figure,figcaption,footer,h1,h2,h3,h4,h5,h6,header,hgroup,hr,ins,map,menu,nav,ol,pre,script,section,table,ul")), |
||||
v=h.extend({},q,k("a,abbr,acronym,b,bdi,bdo,big,br,cite,code,del,dfn,em,font,i,img,ins,kbd,label,map,mark,q,ruby,rp,rt,s,samp,small,span,strike,strong,sub,sup,time,tt,u,var")),y=k("script,style"),D=h.extend({},x,u,v,w),E=k("background,cite,href,longdesc,src,usemap"),O=h.extend({},E,k("abbr,align,alt,axis,bgcolor,border,cellpadding,cellspacing,class,clear,color,cols,colspan,compact,coords,dir,face,headers,height,hreflang,hspace,ismap,lang,language,nohref,nowrap,rel,rev,rows,rowspan,rules,scope,scrolling,shape,span,start,summary,target,title,type,valign,value,vspace,width")), |
||||
s=document.createElement("pre");h.module("ngSanitize",[]).provider("$sanitize",function(){this.$get=["$$sanitizeUri",function(a){return function(e){var d=[];G(e,t(d,function(c,b){return!/^unsafe/.test(a(c,b))}));return d.join("")}}]});h.module("ngSanitize").filter("linky",["$sanitize",function(a){var e=/((ftp|https?):\/\/|(mailto:)?[A-Za-z0-9._%+-]+@)\S*[^\s.;,(){}<>]/,d=/^mailto:/;return function(c,b){function g(a){a&&m.push(F(a))}function f(a,c){m.push("<a ");h.isDefined(b)&&(m.push('target="'), |
||||
m.push(b),m.push('" '));m.push('href="');m.push(a);m.push('">');g(c);m.push("</a>")}if(!c)return c;for(var l,k=c,m=[],p,n;l=k.match(e);)p=l[0],l[2]==l[3]&&(p="mailto:"+p),n=l.index,g(k.substr(0,n)),f(p,l[0].replace(d,"")),k=k.substring(n+l[0].length);g(k);return a(m.join(""))}}])})(window,window.angular); |
||||
//# sourceMappingURL=angular-sanitize.min.js.map
|
@ -0,0 +1,563 @@
|
||||
/** |
||||
* @license AngularJS v1.2.3 |
||||
* (c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
* License: MIT |
||||
*/ |
||||
(function(window, angular, undefined) {'use strict'; |
||||
|
||||
/** |
||||
* @ngdoc overview |
||||
* @name ngTouch |
||||
* @description |
||||
* |
||||
* # ngTouch |
||||
* |
||||
* The `ngTouch` module provides touch events and other helpers for touch-enabled devices. |
||||
* The implementation is based on jQuery Mobile touch event handling
|
||||
* ([jquerymobile.com](http://jquerymobile.com/)).
|
||||
* |
||||
* {@installModule touch} |
||||
* |
||||
* See {@link ngTouch.$swipe `$swipe`} for usage. |
||||
* |
||||
* <div doc-module-components="ngTouch"></div> |
||||
* |
||||
*/ |
||||
|
||||
// define ngTouch module
|
||||
/* global -ngTouch */ |
||||
var ngTouch = angular.module('ngTouch', []); |
||||
|
||||
/* global ngTouch: false */ |
||||
|
||||
/** |
||||
* @ngdoc object |
||||
* @name ngTouch.$swipe |
||||
* |
||||
* @description |
||||
* The `$swipe` service is a service that abstracts the messier details of hold-and-drag swipe |
||||
* behavior, to make implementing swipe-related directives more convenient. |
||||
* |
||||
* Requires the {@link ngTouch `ngTouch`} module to be installed. |
||||
* |
||||
* `$swipe` is used by the `ngSwipeLeft` and `ngSwipeRight` directives in `ngTouch`, and by |
||||
* `ngCarousel` in a separate component. |
||||
* |
||||
* # Usage |
||||
* The `$swipe` service is an object with a single method: `bind`. `bind` takes an element |
||||
* which is to be watched for swipes, and an object with four handler functions. See the |
||||
* documentation for `bind` below. |
||||
*/ |
||||
|
||||
ngTouch.factory('$swipe', [function() { |
||||
// The total distance in any direction before we make the call on swipe vs. scroll.
|
||||
var MOVE_BUFFER_RADIUS = 10; |
||||
|
||||
function getCoordinates(event) { |
||||
var touches = event.touches && event.touches.length ? event.touches : [event]; |
||||
var e = (event.changedTouches && event.changedTouches[0]) || |
||||
(event.originalEvent && event.originalEvent.changedTouches && |
||||
event.originalEvent.changedTouches[0]) || |
||||
touches[0].originalEvent || touches[0]; |
||||
|
||||
return { |
||||
x: e.clientX, |
||||
y: e.clientY |
||||
}; |
||||
} |
||||
|
||||
return { |
||||
/** |
||||
* @ngdoc method |
||||
* @name ngTouch.$swipe#bind |
||||
* @methodOf ngTouch.$swipe |
||||
* |
||||
* @description |
||||
* The main method of `$swipe`. It takes an element to be watched for swipe motions, and an |
||||
* object containing event handlers. |
||||
* |
||||
* The four events are `start`, `move`, `end`, and `cancel`. `start`, `move`, and `end` |
||||
* receive as a parameter a coordinates object of the form `{ x: 150, y: 310 }`. |
||||
* |
||||
* `start` is called on either `mousedown` or `touchstart`. After this event, `$swipe` is |
||||
* watching for `touchmove` or `mousemove` events. These events are ignored until the total |
||||
* distance moved in either dimension exceeds a small threshold. |
||||
* |
||||
* Once this threshold is exceeded, either the horizontal or vertical delta is greater. |
||||
* - If the horizontal distance is greater, this is a swipe and `move` and `end` events follow. |
||||
* - If the vertical distance is greater, this is a scroll, and we let the browser take over. |
||||
* A `cancel` event is sent. |
||||
* |
||||
* `move` is called on `mousemove` and `touchmove` after the above logic has determined that |
||||
* a swipe is in progress. |
||||
* |
||||
* `end` is called when a swipe is successfully completed with a `touchend` or `mouseup`. |
||||
* |
||||
* `cancel` is called either on a `touchcancel` from the browser, or when we begin scrolling |
||||
* as described above. |
||||
* |
||||
*/ |
||||
bind: function(element, eventHandlers) { |
||||
// Absolute total movement, used to control swipe vs. scroll.
|
||||
var totalX, totalY; |
||||
// Coordinates of the start position.
|
||||
var startCoords; |
||||
// Last event's position.
|
||||
var lastPos; |
||||
// Whether a swipe is active.
|
||||
var active = false; |
||||
|
||||
element.on('touchstart mousedown', function(event) { |
||||
startCoords = getCoordinates(event); |
||||
active = true; |
||||
totalX = 0; |
||||
totalY = 0; |
||||
lastPos = startCoords; |
||||
eventHandlers['start'] && eventHandlers['start'](startCoords, event); |
||||
}); |
||||
|
||||
element.on('touchcancel', function(event) { |
||||
active = false; |
||||
eventHandlers['cancel'] && eventHandlers['cancel'](event); |
||||
}); |
||||
|
||||
element.on('touchmove mousemove', function(event) { |
||||
if (!active) return; |
||||
|
||||
// Android will send a touchcancel if it thinks we're starting to scroll.
|
||||
// So when the total distance (+ or - or both) exceeds 10px in either direction,
|
||||
// we either:
|
||||
// - On totalX > totalY, we send preventDefault() and treat this as a swipe.
|
||||
// - On totalY > totalX, we let the browser handle it as a scroll.
|
||||
|
||||
if (!startCoords) return; |
||||
var coords = getCoordinates(event); |
||||
|
||||
totalX += Math.abs(coords.x - lastPos.x); |
||||
totalY += Math.abs(coords.y - lastPos.y); |
||||
|
||||
lastPos = coords; |
||||
|
||||
if (totalX < MOVE_BUFFER_RADIUS && totalY < MOVE_BUFFER_RADIUS) { |
||||
return; |
||||
} |
||||
|
||||
// One of totalX or totalY has exceeded the buffer, so decide on swipe vs. scroll.
|
||||
if (totalY > totalX) { |
||||
// Allow native scrolling to take over.
|
||||
active = false; |
||||
eventHandlers['cancel'] && eventHandlers['cancel'](event); |
||||
return; |
||||
} else { |
||||
// Prevent the browser from scrolling.
|
||||
event.preventDefault(); |
||||
eventHandlers['move'] && eventHandlers['move'](coords, event); |
||||
} |
||||
}); |
||||
|
||||
element.on('touchend mouseup', function(event) { |
||||
if (!active) return; |
||||
active = false; |
||||
eventHandlers['end'] && eventHandlers['end'](getCoordinates(event), event); |
||||
}); |
||||
} |
||||
}; |
||||
}]); |
||||
|
||||
/* global ngTouch: false */ |
||||
|
||||
/** |
||||
* @ngdoc directive |
||||
* @name ngTouch.directive:ngClick |
||||
* |
||||
* @description |
||||
* A more powerful replacement for the default ngClick designed to be used on touchscreen |
||||
* devices. Most mobile browsers wait about 300ms after a tap-and-release before sending |
||||
* the click event. This version handles them immediately, and then prevents the |
||||
* following click event from propagating. |
||||
* |
||||
* Requires the {@link ngTouch `ngTouch`} module to be installed. |
||||
* |
||||
* This directive can fall back to using an ordinary click event, and so works on desktop |
||||
* browsers as well as mobile. |
||||
* |
||||
* This directive also sets the CSS class `ng-click-active` while the element is being held |
||||
* down (by a mouse click or touch) so you can restyle the depressed element if you wish. |
||||
* |
||||
* @element ANY |
||||
* @param {expression} ngClick {@link guide/expression Expression} to evaluate |
||||
* upon tap. (Event object is available as `$event`) |
||||
* |
||||
* @example |
||||
<doc:example> |
||||
<doc:source> |
||||
<button ng-click="count = count + 1" ng-init="count=0"> |
||||
Increment |
||||
</button> |
||||
count: {{ count }} |
||||
</doc:source> |
||||
</doc:example> |
||||
*/ |
||||
|
||||
ngTouch.config(['$provide', function($provide) { |
||||
$provide.decorator('ngClickDirective', ['$delegate', function($delegate) { |
||||
// drop the default ngClick directive
|
||||
$delegate.shift(); |
||||
return $delegate; |
||||
}]); |
||||
}]); |
||||
|
||||
ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement', |
||||
function($parse, $timeout, $rootElement) { |
||||
var TAP_DURATION = 750; // Shorter than 750ms is a tap, longer is a taphold or drag.
|
||||
var MOVE_TOLERANCE = 12; // 12px seems to work in most mobile browsers.
|
||||
var PREVENT_DURATION = 2500; // 2.5 seconds maximum from preventGhostClick call to click
|
||||
var CLICKBUSTER_THRESHOLD = 25; // 25 pixels in any dimension is the limit for busting clicks.
|
||||
|
||||
var ACTIVE_CLASS_NAME = 'ng-click-active'; |
||||
var lastPreventedTime; |
||||
var touchCoordinates; |
||||
|
||||
|
||||
// TAP EVENTS AND GHOST CLICKS
|
||||
//
|
||||
// Why tap events?
|
||||
// Mobile browsers detect a tap, then wait a moment (usually ~300ms) to see if you're
|
||||
// double-tapping, and then fire a click event.
|
||||
//
|
||||
// This delay sucks and makes mobile apps feel unresponsive.
|
||||
// So we detect touchstart, touchmove, touchcancel and touchend ourselves and determine when
|
||||
// the user has tapped on something.
|
||||
//
|
||||
// What happens when the browser then generates a click event?
|
||||
// The browser, of course, also detects the tap and fires a click after a delay. This results in
|
||||
// tapping/clicking twice. So we do "clickbusting" to prevent it.
|
||||
//
|
||||
// How does it work?
|
||||
// We attach global touchstart and click handlers, that run during the capture (early) phase.
|
||||
// So the sequence for a tap is:
|
||||
// - global touchstart: Sets an "allowable region" at the point touched.
|
||||
// - element's touchstart: Starts a touch
|
||||
// (- touchmove or touchcancel ends the touch, no click follows)
|
||||
// - element's touchend: Determines if the tap is valid (didn't move too far away, didn't hold
|
||||
// too long) and fires the user's tap handler. The touchend also calls preventGhostClick().
|
||||
// - preventGhostClick() removes the allowable region the global touchstart created.
|
||||
// - The browser generates a click event.
|
||||
// - The global click handler catches the click, and checks whether it was in an allowable region.
|
||||
// - If preventGhostClick was called, the region will have been removed, the click is busted.
|
||||
// - If the region is still there, the click proceeds normally. Therefore clicks on links and
|
||||
// other elements without ngTap on them work normally.
|
||||
//
|
||||
// This is an ugly, terrible hack!
|
||||
// Yeah, tell me about it. The alternatives are using the slow click events, or making our users
|
||||
// deal with the ghost clicks, so I consider this the least of evils. Fortunately Angular
|
||||
// encapsulates this ugly logic away from the user.
|
||||
//
|
||||
// Why not just put click handlers on the element?
|
||||
// We do that too, just to be sure. The problem is that the tap event might have caused the DOM
|
||||
// to change, so that the click fires in the same position but something else is there now. So
|
||||
// the handlers are global and care only about coordinates and not elements.
|
||||
|
||||
// Checks if the coordinates are close enough to be within the region.
|
||||
function hit(x1, y1, x2, y2) { |
||||
return Math.abs(x1 - x2) < CLICKBUSTER_THRESHOLD && Math.abs(y1 - y2) < CLICKBUSTER_THRESHOLD; |
||||
} |
||||
|
||||
// Checks a list of allowable regions against a click location.
|
||||
// Returns true if the click should be allowed.
|
||||
// Splices out the allowable region from the list after it has been used.
|
||||
function checkAllowableRegions(touchCoordinates, x, y) { |
||||
for (var i = 0; i < touchCoordinates.length; i += 2) { |
||||
if (hit(touchCoordinates[i], touchCoordinates[i+1], x, y)) { |
||||
touchCoordinates.splice(i, i + 2); |
||||
return true; // allowable region
|
||||
} |
||||
} |
||||
return false; // No allowable region; bust it.
|
||||
} |
||||
|
||||
// Global click handler that prevents the click if it's in a bustable zone and preventGhostClick
|
||||
// was called recently.
|
||||
function onClick(event) { |
||||
if (Date.now() - lastPreventedTime > PREVENT_DURATION) { |
||||
return; // Too old.
|
||||
} |
||||
|
||||
var touches = event.touches && event.touches.length ? event.touches : [event]; |
||||
var x = touches[0].clientX; |
||||
var y = touches[0].clientY; |
||||
// Work around desktop Webkit quirk where clicking a label will fire two clicks (on the label
|
||||
// and on the input element). Depending on the exact browser, this second click we don't want
|
||||
// to bust has either (0,0) or negative coordinates.
|
||||
if (x < 1 && y < 1) { |
||||
return; // offscreen
|
||||
} |
||||
|
||||
// Look for an allowable region containing this click.
|
||||
// If we find one, that means it was created by touchstart and not removed by
|
||||
// preventGhostClick, so we don't bust it.
|
||||
if (checkAllowableRegions(touchCoordinates, x, y)) { |
||||
return; |
||||
} |
||||
|
||||
// If we didn't find an allowable region, bust the click.
|
||||
event.stopPropagation(); |
||||
event.preventDefault(); |
||||
|
||||
// Blur focused form elements
|
||||
event.target && event.target.blur(); |
||||
} |
||||
|
||||
|
||||
// Global touchstart handler that creates an allowable region for a click event.
|
||||
// This allowable region can be removed by preventGhostClick if we want to bust it.
|
||||
function onTouchStart(event) { |
||||
var touches = event.touches && event.touches.length ? event.touches : [event]; |
||||
var x = touches[0].clientX; |
||||
var y = touches[0].clientY; |
||||
touchCoordinates.push(x, y); |
||||
|
||||
$timeout(function() { |
||||
// Remove the allowable region.
|
||||
for (var i = 0; i < touchCoordinates.length; i += 2) { |
||||
if (touchCoordinates[i] == x && touchCoordinates[i+1] == y) { |
||||
touchCoordinates.splice(i, i + 2); |
||||
return; |
||||
} |
||||
} |
||||
}, PREVENT_DURATION, false); |
||||
} |
||||
|
||||
// On the first call, attaches some event handlers. Then whenever it gets called, it creates a
|
||||
// zone around the touchstart where clicks will get busted.
|
||||
function preventGhostClick(x, y) { |
||||
if (!touchCoordinates) { |
||||
$rootElement[0].addEventListener('click', onClick, true); |
||||
$rootElement[0].addEventListener('touchstart', onTouchStart, true); |
||||
touchCoordinates = []; |
||||
} |
||||
|
||||
lastPreventedTime = Date.now(); |
||||
|
||||
checkAllowableRegions(touchCoordinates, x, y); |
||||
} |
||||
|
||||
// Actual linking function.
|
||||
return function(scope, element, attr) { |
||||
var clickHandler = $parse(attr.ngClick), |
||||
tapping = false, |
||||
tapElement, // Used to blur the element after a tap.
|
||||
startTime, // Used to check if the tap was held too long.
|
||||
touchStartX, |
||||
touchStartY; |
||||
|
||||
function resetState() { |
||||
tapping = false; |
||||
element.removeClass(ACTIVE_CLASS_NAME); |
||||
} |
||||
|
||||
element.on('touchstart', function(event) { |
||||
tapping = true; |
||||
tapElement = event.target ? event.target : event.srcElement; // IE uses srcElement.
|
||||
// Hack for Safari, which can target text nodes instead of containers.
|
||||
if(tapElement.nodeType == 3) { |
||||
tapElement = tapElement.parentNode; |
||||
} |
||||
|
||||
element.addClass(ACTIVE_CLASS_NAME); |
||||
|
||||
startTime = Date.now(); |
||||
|
||||
var touches = event.touches && event.touches.length ? event.touches : [event]; |
||||
var e = touches[0].originalEvent || touches[0]; |
||||
touchStartX = e.clientX; |
||||
touchStartY = e.clientY; |
||||
}); |
||||
|
||||
element.on('touchmove', function(event) { |
||||
resetState(); |
||||
}); |
||||
|
||||
element.on('touchcancel', function(event) { |
||||
resetState(); |
||||
}); |
||||
|
||||
element.on('touchend', function(event) { |
||||
var diff = Date.now() - startTime; |
||||
|
||||
var touches = (event.changedTouches && event.changedTouches.length) ? event.changedTouches : |
||||
((event.touches && event.touches.length) ? event.touches : [event]); |
||||
var e = touches[0].originalEvent || touches[0]; |
||||
var x = e.clientX; |
||||
var y = e.clientY; |
||||
var dist = Math.sqrt( Math.pow(x - touchStartX, 2) + Math.pow(y - touchStartY, 2) ); |
||||
|
||||
if (tapping && diff < TAP_DURATION && dist < MOVE_TOLERANCE) { |
||||
// Call preventGhostClick so the clickbuster will catch the corresponding click.
|
||||
preventGhostClick(x, y); |
||||
|
||||
// Blur the focused element (the button, probably) before firing the callback.
|
||||
// This doesn't work perfectly on Android Chrome, but seems to work elsewhere.
|
||||
// I couldn't get anything to work reliably on Android Chrome.
|
||||
if (tapElement) { |
||||
tapElement.blur(); |
||||
} |
||||
|
||||
if (!angular.isDefined(attr.disabled) || attr.disabled === false) { |
||||
element.triggerHandler('click', [event]); |
||||
} |
||||
} |
||||
|
||||
resetState(); |
||||
}); |
||||
|
||||
// Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click
|
||||
// something else nearby.
|
||||
element.onclick = function(event) { }; |
||||
|
||||
// Actual click handler.
|
||||
// There are three different kinds of clicks, only two of which reach this point.
|
||||
// - On desktop browsers without touch events, their clicks will always come here.
|
||||
// - On mobile browsers, the simulated "fast" click will call this.
|
||||
// - But the browser's follow-up slow click will be "busted" before it reaches this handler.
|
||||
// Therefore it's safe to use this directive on both mobile and desktop.
|
||||
element.on('click', function(event, touchend) { |
||||
scope.$apply(function() { |
||||
clickHandler(scope, {$event: (touchend || event)}); |
||||
}); |
||||
}); |
||||
|
||||
element.on('mousedown', function(event) { |
||||
element.addClass(ACTIVE_CLASS_NAME); |
||||
}); |
||||
|
||||
element.on('mousemove mouseup', function(event) { |
||||
element.removeClass(ACTIVE_CLASS_NAME); |
||||
}); |
||||
|
||||
}; |
||||
}]); |
||||
|
||||
/* global ngTouch: false */ |
||||
|
||||
/** |
||||
* @ngdoc directive |
||||
* @name ngTouch.directive:ngSwipeLeft |
||||
* |
||||
* @description |
||||
* Specify custom behavior when an element is swiped to the left on a touchscreen device. |
||||
* A leftward swipe is a quick, right-to-left slide of the finger. |
||||
* Though ngSwipeLeft is designed for touch-based devices, it will work with a mouse click and drag |
||||
* too. |
||||
* |
||||
* Requires the {@link ngTouch `ngTouch`} module to be installed. |
||||
* |
||||
* @element ANY |
||||
* @param {expression} ngSwipeLeft {@link guide/expression Expression} to evaluate |
||||
* upon left swipe. (Event object is available as `$event`) |
||||
* |
||||
* @example |
||||
<doc:example> |
||||
<doc:source> |
||||
<div ng-show="!showActions" ng-swipe-left="showActions = true"> |
||||
Some list content, like an email in the inbox |
||||
</div> |
||||
<div ng-show="showActions" ng-swipe-right="showActions = false"> |
||||
<button ng-click="reply()">Reply</button> |
||||
<button ng-click="delete()">Delete</button> |
||||
</div> |
||||
</doc:source> |
||||
</doc:example> |
||||
*/ |
||||
|
||||
/** |
||||
* @ngdoc directive |
||||
* @name ngTouch.directive:ngSwipeRight |
||||
* |
||||
* @description |
||||
* Specify custom behavior when an element is swiped to the right on a touchscreen device. |
||||
* A rightward swipe is a quick, left-to-right slide of the finger. |
||||
* Though ngSwipeRight is designed for touch-based devices, it will work with a mouse click and drag |
||||
* too. |
||||
* |
||||
* Requires the {@link ngTouch `ngTouch`} module to be installed. |
||||
* |
||||
* @element ANY |
||||
* @param {expression} ngSwipeRight {@link guide/expression Expression} to evaluate |
||||
* upon right swipe. (Event object is available as `$event`) |
||||
* |
||||
* @example |
||||
<doc:example> |
||||
<doc:source> |
||||
<div ng-show="!showActions" ng-swipe-left="showActions = true"> |
||||
Some list content, like an email in the inbox |
||||
</div> |
||||
<div ng-show="showActions" ng-swipe-right="showActions = false"> |
||||
<button ng-click="reply()">Reply</button> |
||||
<button ng-click="delete()">Delete</button> |
||||
</div> |
||||
</doc:source> |
||||
</doc:example> |
||||
*/ |
||||
|
||||
function makeSwipeDirective(directiveName, direction, eventName) { |
||||
ngTouch.directive(directiveName, ['$parse', '$swipe', function($parse, $swipe) { |
||||
// The maximum vertical delta for a swipe should be less than 75px.
|
||||
var MAX_VERTICAL_DISTANCE = 75; |
||||
// Vertical distance should not be more than a fraction of the horizontal distance.
|
||||
var MAX_VERTICAL_RATIO = 0.3; |
||||
// At least a 30px lateral motion is necessary for a swipe.
|
||||
var MIN_HORIZONTAL_DISTANCE = 30; |
||||
|
||||
return function(scope, element, attr) { |
||||
var swipeHandler = $parse(attr[directiveName]); |
||||
|
||||
var startCoords, valid; |
||||
|
||||
function validSwipe(coords) { |
||||
// Check that it's within the coordinates.
|
||||
// Absolute vertical distance must be within tolerances.
|
||||
// Horizontal distance, we take the current X - the starting X.
|
||||
// This is negative for leftward swipes and positive for rightward swipes.
|
||||
// After multiplying by the direction (-1 for left, +1 for right), legal swipes
|
||||
// (ie. same direction as the directive wants) will have a positive delta and
|
||||
// illegal ones a negative delta.
|
||||
// Therefore this delta must be positive, and larger than the minimum.
|
||||
if (!startCoords) return false; |
||||
var deltaY = Math.abs(coords.y - startCoords.y); |
||||
var deltaX = (coords.x - startCoords.x) * direction; |
||||
return valid && // Short circuit for already-invalidated swipes.
|
||||
deltaY < MAX_VERTICAL_DISTANCE && |
||||
deltaX > 0 && |
||||
deltaX > MIN_HORIZONTAL_DISTANCE && |
||||
deltaY / deltaX < MAX_VERTICAL_RATIO; |
||||
} |
||||
|
||||
$swipe.bind(element, { |
||||
'start': function(coords, event) { |
||||
startCoords = coords; |
||||
valid = true; |
||||
}, |
||||
'cancel': function(event) { |
||||
valid = false; |
||||
}, |
||||
'end': function(coords, event) { |
||||
if (validSwipe(coords)) { |
||||
scope.$apply(function() { |
||||
element.triggerHandler(eventName); |
||||
swipeHandler(scope, {$event: event}); |
||||
}); |
||||
} |
||||
} |
||||
}); |
||||
}; |
||||
}]); |
||||
} |
||||
|
||||
// Left is negative X-coordinate, right is positive.
|
||||
makeSwipeDirective('ngSwipeLeft', -1, 'swipeleft'); |
||||
makeSwipeDirective('ngSwipeRight', 1, 'swiperight'); |
||||
|
||||
|
||||
|
||||
})(window, window.angular); |
@ -0,0 +1,13 @@
|
||||
/* |
||||
AngularJS v1.2.3 |
||||
(c) 2010-2014 Google, Inc. http://angularjs.org
|
||||
License: MIT |
||||
*/ |
||||
(function(y,v,z){'use strict';function t(g,a,b){q.directive(g,["$parse","$swipe",function(l,n){var r=75,h=0.3,d=30;return function(p,m,k){function e(e){if(!u)return!1;var c=Math.abs(e.y-u.y);e=(e.x-u.x)*a;return f&&c<r&&0<e&&e>d&&c/e<h}var c=l(k[g]),u,f;n.bind(m,{start:function(e,c){u=e;f=!0},cancel:function(e){f=!1},end:function(a,f){e(a)&&p.$apply(function(){m.triggerHandler(b);c(p,{$event:f})})}})}}])}var q=v.module("ngTouch",[]);q.factory("$swipe",[function(){function g(a){var b=a.touches&&a.touches.length? |
||||
a.touches:[a];a=a.changedTouches&&a.changedTouches[0]||a.originalEvent&&a.originalEvent.changedTouches&&a.originalEvent.changedTouches[0]||b[0].originalEvent||b[0];return{x:a.clientX,y:a.clientY}}return{bind:function(a,b){var l,n,r,h,d=!1;a.on("touchstart mousedown",function(a){r=g(a);d=!0;n=l=0;h=r;b.start&&b.start(r,a)});a.on("touchcancel",function(a){d=!1;b.cancel&&b.cancel(a)});a.on("touchmove mousemove",function(a){if(d&&r){var m=g(a);l+=Math.abs(m.x-h.x);n+=Math.abs(m.y-h.y);h=m;10>l&&10>n|| |
||||
(n>l?(d=!1,b.cancel&&b.cancel(a)):(a.preventDefault(),b.move&&b.move(m,a)))}});a.on("touchend mouseup",function(a){d&&(d=!1,b.end&&b.end(g(a),a))})}}}]);q.config(["$provide",function(g){g.decorator("ngClickDirective",["$delegate",function(a){a.shift();return a}])}]);q.directive("ngClick",["$parse","$timeout","$rootElement",function(g,a,b){function l(a,c,b){for(var f=0;f<a.length;f+=2)if(Math.abs(a[f]-c)<d&&Math.abs(a[f+1]-b)<d)return a.splice(f,f+2),!0;return!1}function n(a){if(!(Date.now()-m>h)){var c= |
||||
a.touches&&a.touches.length?a.touches:[a],b=c[0].clientX,c=c[0].clientY;1>b&&1>c||l(k,b,c)||(a.stopPropagation(),a.preventDefault(),a.target&&a.target.blur())}}function r(b){b=b.touches&&b.touches.length?b.touches:[b];var c=b[0].clientX,d=b[0].clientY;k.push(c,d);a(function(){for(var a=0;a<k.length;a+=2)if(k[a]==c&&k[a+1]==d){k.splice(a,a+2);break}},h,!1)}var h=2500,d=25,p="ng-click-active",m,k;return function(a,c,d){function f(){q=!1;c.removeClass(p)}var h=g(d.ngClick),q=!1,s,t,w,x;c.on("touchstart", |
||||
function(a){q=!0;s=a.target?a.target:a.srcElement;3==s.nodeType&&(s=s.parentNode);c.addClass(p);t=Date.now();a=a.touches&&a.touches.length?a.touches:[a];a=a[0].originalEvent||a[0];w=a.clientX;x=a.clientY});c.on("touchmove",function(a){f()});c.on("touchcancel",function(a){f()});c.on("touchend",function(a){var h=Date.now()-t,e=a.changedTouches&&a.changedTouches.length?a.changedTouches:a.touches&&a.touches.length?a.touches:[a],g=e[0].originalEvent||e[0],e=g.clientX,g=g.clientY,p=Math.sqrt(Math.pow(e- |
||||
w,2)+Math.pow(g-x,2));q&&(750>h&&12>p)&&(k||(b[0].addEventListener("click",n,!0),b[0].addEventListener("touchstart",r,!0),k=[]),m=Date.now(),l(k,e,g),s&&s.blur(),v.isDefined(d.disabled)&&!1!==d.disabled||c.triggerHandler("click",[a]));f()});c.onclick=function(a){};c.on("click",function(b,c){a.$apply(function(){h(a,{$event:c||b})})});c.on("mousedown",function(a){c.addClass(p)});c.on("mousemove mouseup",function(a){c.removeClass(p)})}}]);t("ngSwipeLeft",-1,"swipeleft");t("ngSwipeRight",1,"swiperight")})(window, |
||||
window.angular); |
||||
//# sourceMappingURL=angular-touch.min.js.map
|